Description
Proposal Details
This is #64777 (comment) in proposal form. It is a reduced and compatible variant of #64777 (comment).
I propose to add a new package, mainthread
, with a single function, Do
, that allows Go programs to execute a function on the main thread.
// Package mainthread mediates access to the program's main thread.
//
// Most Go programs do not need to run on specific threads
// and can ignore this package, but some C libraries, often GUI-related libraries,
// only work when invoked from the program's main thread.
//
// [Do] runs a function on the main thread. No other code can run on the main thread
// until that function returns.
//
// Each package's initialization functions always run on the main thread,
// as if by successive calls to Do(init).
//
// For compatibility with earlier versions of Go, if an init function calls [runtime.LockOSThread],
// then package main's func main also runs on the main thread, as if by Do(main).
package mainthread // imported as "runtime/mainthread"
// Do calls f on the main thread.
// Nothing else runs on the main thread until f returns.
// If f calls Do, the nested call panics.
//
// Package initialization functions run as if by Do(init).
// If an init function calls [runtime.LockOSThread], then package main's func main
// runs as if by Do(main), until the thread is unlocked using [runtime.UnlockOSThread].
//
// Do panics if the Go runtime is not in control of the main thread, such as in build modes
// c-shared and c-archive.
func Do(f func())
The larger proposal (#64777 (comment)) adds Yield
and Waiting
to support sharing the main thread in a Go program. However, the Go runtime doesn't always have control over the main thread, most notably in c-shared or c-archive mode on platforms such as Android. In those cases, the platform facility for mediating main thread access are strictly superior to mainthread.Do
. See #64777 (comment) for a detailed analysis and assumptions.
In short, I believe it's better to accept this simpler proposal to only allow Go programs access to the main thread when the Go runtime has control over it, and let other cases be handled by platform API.
I hope this can be implemented in Go 1.24.
Metadata
Metadata
Assignees
Type
Projects
Status
Activity
gabyhelp commentedon Oct 29, 2024
Related Issues and Documentation
(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)
apparentlymart commentedon Oct 29, 2024
(I see that earlier versions of this proposal were already discussed at length elsewhere and I did try to catch up on it first, but I apologize if I'm asking a question that's redundant from earlier discussions.)
If this function will panic when called from an environment where the Go runtime does not "own" the main thread, is it justified to also offer a function to test whether a call to this function is possible? That could, for example, allow a caller to choose to treat "I'm running in the wrong mode" as an error to be handled gracefully, rather than as an exception to be handled by panicking.
(Another variation of this would be for
Do
itself to return anerror
, but the usability of not having to worry about error handling when you know you're running in a context where this should work seems nice... this concern of detecting whether it will succeed seems specific to library developers that want their library to degrade gracefully inc-shared
/c-archive
/etc build modes.)Jorropo commentedon Oct 29, 2024
Just so you know, the current merge window closes 21 11, this would be a quick turn around time. There is the option of getting exceptions but theses are rare and usually limited to very low dangerous community impact.
qiulaidongfeng commentedon Oct 29, 2024
Does this API mean that if the main package imports a package that calls runtime.LockOSThread in init (for event loop in main thread) ,
like fyne did, calls to mainthread.Do by other packages will block permanently?
If so, that means we may need to modify existing valid code when using the mainthread package, which I don't think is backward-compatible,see #64777 (comment).
Fyne Info:
On Windows:
call LockOSThread in https://github.com/fyne-io/fyne/blob/7d813563712924b381ced18c04869c059e2cb4c6/internal/driver/glfw/loop.go#L35
event loop in https://github.com/fyne-io/fyne/blob/7d813563712924b381ced18c04869c059e2cb4c6/internal/driver/glfw/loop.go#L107
eliasnaur commentedon Oct 31, 2024
@qiulaidongfeng I believe your comment is addressed by #64777 (comment). In short,
LockOSThread
during init does not compose automatically withmainthread.Do
, because they both act on a single resource, the main thread. There is no backwards compatibility issue, however, because this proposal doesn't affect the behaviour ofLockOSThread
during init.eliasnaur commentedon Oct 31, 2024
@apparentlymart the original proposal says to panic in c-shared/c-archive mode, but I'm not against CanDo or the like.
gopherbot commentedon Nov 17, 2024
Change https://go.dev/cl/628815 mentions this issue:
runtime/mainthread: new package
rsc commentedon Dec 4, 2024
This proposal has been added to the active column of the proposals project
and will now be reviewed at the weekly proposal review meetings.
— rsc for the proposal review group
aclements commentedon Dec 19, 2024
I think we need to re-ground this discussion in concrete use cases. I'm sure at least some of this will be me asking you to repeat what's already been said in #64777, but I think getting re-consolidating this information will be helpful.
Let's define main thread to mean the OS-created thread that started the process, and define startup thread to mean the thread we run Go init functions on. In typical Go binaries, these are one and the same. In c-shared and c-archive mode, the Go runtime always creates a new thread to run init functions, and exits that thread after init functions are done, so the startup thread is not the main thread. There's also a library load thread, which is the thread that first calls into the Go runtime in c-shared and c-archive mode. This may be the main thread or may be another thread, but the Go runtime relinquishes control of this thread very quickly.
What are the situations where a library needs to be called on the main thread (and not just consistently on some thread, and not just on the startup thread), and the platform doesn't provide a mechanism for calling code on the main thread? Can you give concrete examples so we have something to ground the requirements in?
How are libraries even sensitive to this? Do they behave differently from C if you link against them at build time (statically or dynamically) versus if you
dlopen
them at run time? (The abstract case we were able to come up with in proposal review is that a native library has global constructors/ELF initializers and is sensitive to other functions running on the same thread. But even that isn't necessarily the main thread if that library getsdlopen
ed.)63 remaining items