runtime: document that calling LockOSThread in init will lock main to m0 (the first OS thread) #23112
Labels
Documentation
FrozenDueToAge
NeedsFix
The path to resolution is known, but the work has not been done.
Milestone
Continued from a brief in-person discussion with Ian and Keith earlier today.
A lot of people (including myself) want to write GUI programs in Go; we even had
shiny
to provide our own solution to this desire. Most, if not all OSs, require that all GUI code runs on a single OS thread, but most don't care which OS thread is used. macOS is the exception — macOS consigns the idea that the very first thread created by the OS is the only one allowed to do GUI stuff, and it has a few ways to do this:pthread_main_np()
function, which returns true if the current thread is the first thread the OS creates (and in the Darwin kernel, pthreads are the kernel threading model, in essence); WebKit calls this function directly__attribute__((constructor))
to call a variety of thread-specific initialization functions in other libraries (such as libdispatch, which is important for modern Cocoa programming)extern
statements, will not catch all possible casesVarious components in Cocoa have internal consistency checks to make sure they are only ever called on the "main thread"; a program will abort if these checks fail.
In runtime-speak, the first thread created by the OS is called
m0
(or was calledm0
back when the runtime was written in C), so I will call it that from here on out.On other platforms, we could just insert a
runtime.LockOSThread()
in a goroutine to tie the goroutine that will do all GUI stuff to an OS thread, but for macOS this alone wouldn't work. I have found a number of ways to bypass some of the above bullet points, but things just wind up breaking later (and I don't think the libdispatch thing can be hacked without hard-coding its internal data structures...).And this doesn't even consider things like transpiled JavaScript (in which I don't even know what the requirements are) or mobile platforms (I'm sure iOS has the same requirements as macOS but I'm not sure about any other platform).
So far, the most widely used workaround has been to call
runtime.LockOSThread()
ininit
. I didn't think this was a good solution because there was no guarantee thatinit
will always stay onm0
, especially onceinit
was allowed to run concurrently with other goroutines. However, after talking with Ian and Keith earlier today, I learned that this is already guaranteed, just not documented. If this was documented, then we could all safely rely on the behavior.I wouldn't know if this should be documented in package runtime (under LockOSThread) or in the spec (as part of the definition of initialization behavior).
Furthermore, there's the potential that a different package's
init
will callruntime.UnlockOSThread()
, most likely to undo its ownruntime.LockOSThread()
— in which case,LockOSThread
should be nested. Nesting will handle the case of a later package callingUnlockOSThread
, but not of an earlier one doing so too, so perhapsUnlockOSThread
should also be made a no-op ininit
functions, if that's even possible.And of course, all bets are off if another function in
main
callsUnlockOSThread
. Even if this is also made a no-op (which is probably not a good idea), package authors will need to document what functions must be run inmain
. This isn't a problem, though; it's something package authors should know, though.The text was updated successfully, but these errors were encountered: