Is it expected that signal.NotifyContext() changes the execution thread?

351 views
Skip to first unread message

Kurtis Rader

unread,
Sep 29, 2023, 11:20:47 PM9/29/23
to golang-nuts
I was rewriting a program I originally wrote in Python many years ago that uses OpenCV. I wanted to make it possible to cleanly terminate the Go version when SIGINT was sent to the process; e.g., by pressing Ctrl-C. So I changed how the context was created from this in the main() function:

ctx, cancel := context.WithCancel(context.Background())

to this:

ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT)

However, that appeared to change the thread that subsequent statements in main() ran on. I say this because the gocv.NewWindow() function (from package gocv.io/x/gocv and also executed in main()) started complaining that it was no longer running on the main thread. The signal package documentation makes no mention of this side-effect. Am I missing something obvious? Should I open an issue suggesting this side-effect be explicitly documented? Or, is this side-effect a bug?

--
Kurtis Rader
Caretaker of the exceptional canines Junior and Hank

TheDiveO

unread,
Sep 30, 2023, 3:23:24 AM9/30/23
to golang-nuts
Did you explicitly lock the initial OS thread, aka M0, to the main/initial go routine by calling runtime.LockOSThread() from main or an init func? I suspect you were lucky in the past, but I might be wrong.

Kurtis Rader

unread,
Oct 1, 2023, 2:37:08 AM10/1/23
to TheDiveO, golang-nuts
On Sat, Sep 30, 2023 at 12:23 AM TheDiveO <harald....@gmx.net> wrote:
Did you explicitly lock the initial OS thread, aka M0, to the main/initial go routine by calling runtime.LockOSThread() from main or an init func? I suspect you were lucky in the past, but I might be wrong.

No, I did not explicitly lock the initial thread using runtime.LockOSThread(). However, I have run the program hundreds of times, and for tens of hours, while working on it and not once, before introducing signal.NotifyContext()did I see an instance of the gocv package complaining about the thread it was running on not being the main thread. It is certainly possible I have simply been lucky but that seems unlikely since it fails every single time I run it when using signal.NotifyContext().

Kurtis Rader

unread,
Oct 1, 2023, 2:56:57 AM10/1/23
to TheDiveO, golang-nuts
And having said that I just tested using runtime.LockOSThread() and it does allow me to use the more obvious, natural, way to write the main() function. Which still begs the question of why calling signal.NotifyContext() causes subsequent code to run on a different OS thread. If nothing else this seems like a side-effect that should be documented; along with the recommendation of using runtime.LockOSThread() to avoid that side-effect.

TheDiveO

unread,
Oct 1, 2023, 10:14:43 AM10/1/23
to golang-nuts
  Weeell, I had unit tests running without hiccup on a wide range of devices, and only a year later some of them started to fail with a seemingly "leaked" (non-restored) namespace. The question here and the suggested issue then introduced me to the "wedged"  M0 thread.  It works until it doesn't.                  

Ian Lance Taylor

unread,
Oct 2, 2023, 8:05:33 PM10/2/23
to Kurtis Rader, TheDiveO, golang-nuts
On Sat, Sep 30, 2023 at 11:56 PM Kurtis Rader <kra...@skepticism.us> wrote:
>
> And having said that I just tested using runtime.LockOSThread() and it does allow me to use the more obvious, natural, way to write the main() function. Which still begs the question of why calling signal.NotifyContext() causes subsequent code to run on a different OS thread. If nothing else this seems like a side-effect that should be documented; along with the recommendation of using runtime.LockOSThread() to avoid that side-effect.

Goroutines are multiplexed onto operating system threads. A goroutine
that does not call runtime.LockOSThread can move to a different
operating system thread at (almost) any time. The goroutine scheduler
can and does move goroutines between threads without regard to what
those goroutines are doing. I don't know why this is happening
particularly for signal.NotifyContext, but whatever the reason is,
it's unlikely to be interesting. If your code cares about which
operating system thread it runs on, it must use runtime.LockOSThread.

Ian

Kurtis Rader

unread,
Oct 3, 2023, 1:08:41 AM10/3/23
to Ian Lance Taylor, TheDiveO, golang-nuts
Thank you to Ian and TheDiveO. I don't understand why functions like gocv.io/x/gocv.NewWindow() have to run on the initial OS thread (at least on macOS). But adding this to my main package stopped the gocv package from panicking:

func init() {
    runtime.LockOSThread()
}

Is there some reason that locking the main() function to the initial thread isn't the default?

Luke Crook

unread,
Oct 3, 2023, 1:37:41 AM10/3/23
to Kurtis Rader, golang-nuts
It's a thing. In https://www.libsdl.org/ all rendering calls have to be performed in the main thread. And I believe in Windows, the event loop has to run in the same thread that created the window.

/Luke

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/CABx2%3DD__QffcCUPxDDOEgNEz0gBg4%3DAffBzNnJW04o29Ax2Vuw%40mail.gmail.com.

wagner riffel

unread,
Oct 3, 2023, 3:01:01 AM10/3/23
to Kurtis Rader, Ian Lance Taylor, TheDiveO, golang-nuts
On Tue Oct 3, 2023 at 05:54 AM UTC, Kurtis Rader wrote:
> Thank you to Ian and TheDiveO. I don't understand why functions like
> gocv.io/x/gocv.NewWindow() have to run on the initial OS thread (at least
> on macOS).

It's common for C and C++ libraries to use TLS (thread local storage)
to attach data/state to each thread, one example is the global errno
variable used in C to signal errors, each thread read/write its own
variable even though in code they are wirting "the same" variable,
using libc from cgo and reading errno for failures would give wrong
results if the goroutine moved OS threads. It's unrelated which thread
it is and that's why it's not a default, you could start NewWindow at
some point that its goroutine is running in thread4, and if it's not
pinned to run in thread4 you have the same issue with the "initial
thread".

ps: Specific with graphics, I know OpenGL retains thread-local data,
which might explain why libraries that have this common ancestor needs
to LockOSThread, I'm not sure about Mac and Windows.

-w

Robert Engels

unread,
Oct 3, 2023, 6:25:41 AM10/3/23
to wagner riffel, Kurtis Rader, Ian Lance Taylor, TheDiveO, golang-nuts
Almost all graphics systems are single threaded. Some allow additional non rendering event loops but with heavy restrictions that GUI components are only accessed on the main event loop thread.

> On Oct 3, 2023, at 2:00 AM, 'wagner riffel' via golang-nuts <golan...@googlegroups.com> wrote:
> --
> You received this message because you are subscribed to the Google Groups "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/dcbc6ba0-54c8-5942-eb4d-120ba8d65f03%40104d.net.
Reply all
Reply to author
Forward
0 new messages