Go called by C: is the goroutine locked to a thread?

580 views
Skip to first unread message

Domenico Andreoli

unread,
Oct 23, 2023, 8:34:28 AM10/23/23
to golang-nuts
Hi,

  I'm writing a Go library to embed/extend CPython (Pygolo). CPython uses thread local storage and therefore I need to care about pinning goroutines to OS threads accordingly.

It's pretty much clear that when the Python interpreter is embedded it can be accessed only from one goroutine and that such goroutine must be locked to an OS thread all the time.

It's possible to create Python thread states and allow multiple threads to access the interpreter, therefore other goroutines - if locked to an OS thread - can access the interpreter (modulo Python GIL).

It's also clear that if the Python interpreter calls back a Go function it will happen from one of the locked threads of above and therefore nothing special needs to be done on the Go side.

Or not? If a thread is locked no other goroutines are allowed to run as per documentation of LockOSThread, so on which goroutine the Go callback will actually run?

Different story is when Go is used to extend the Python interpreter, when Go calls are happening in a thread not owned by the Go runtime. Here I have some doubts, I think I read something about cgo locking threads of Go callbacks but I can't find it any more. 

I guess that at the first Go call cgo initializes the Go runtime and suddenly a few threads and goroutines spawn to life but how the goroutine scheduling works when C is in control of the calling threads?

Is it possible that multiple goroutines get scheduled on the caller C thread?

Are Go callbacks goroutines reused? If not and I leave a Go callback goroutine locked to a thread, is the calling C thread going to be killed, as per documentation of LockOSThread?

Thanks in advance for any clarification.

Regards,
Dom

--
rsa4096: 3B10 0CA1 8674 ACBA B4FE  FCD2 CE5B CF17 9960 DE13
ed25519: FFB4 0CC3 7F2E 091D F7DA  356E CC79 2832 ED38 CB05


Bryan C. Mills

unread,
Oct 24, 2023, 12:58:22 PM10/24/23
to golang-nuts
If a C thread calls into Go, the goroutine processing that call (and only that goroutine) will run on the C thread. The Go runtime will initialize any of its own per-thread state for that thread as needed. If a goroutine calls into C, and that C thread calls back into Go, I believe that the C-to-Go call will be on the same goroutine as the Go-to-C call.

So if you have a chain of cross-language calls (say, Go to C to Go to C to Go), that should still only consume one OS thread in total.

On the other hand, if you have a goroutine without any C call frames, and that goroutine may have thread-local data for the Python interpreter, you should explicitly call LockOSThread on that goroutine — and never unlock it! — to avoid that data migrating unexpectedly to another goroutine. See https://go.dev/issue/20395.

Domenico Andreoli

unread,
Oct 25, 2023, 10:03:07 AM10/25/23
to Bryan C. Mills, golang-nuts
On Tue, Oct 24, 2023 at 09:58:22AM -0700, 'Bryan C. Mills' via golang-nuts wrote:
> If a C thread calls into Go, the goroutine processing that call (and only
> that goroutine) will run on the C thread. The Go runtime will initialize

Is this thanks to the `lockOSThread` call in function `cgocallbackg` of
https://github.com/golang/go/blob/master/src/runtime/cgocall.go?

> any of its own per-thread state for that thread as needed. If a goroutine
> calls into C, and that C thread calls back into Go, I believe that the
> C-to-Go call will be on the same goroutine as the Go-to-C call.

This is great news.

> So if you have a chain of cross-language calls (say, Go to C to Go to C to
> Go), that should still only consume one OS thread in total.

That's good. I found it also explained here
https://groups.google.com/g/golang-nuts/c/8Lx2TUzeXQE/m/3yl0A-wPEAAJ.

>
> On the other hand, if you have a goroutine without any C call frames, and
> that goroutine may have thread-local data for the Python interpreter, you
> should explicitly call LockOSThread on that goroutine — and never unlock
> it! — to avoid that data migrating unexpectedly to another goroutine.
> See https://go.dev/issue/20395.

That's a very interesting and eye opening link, thanks a lot.

Dom

>
> On Monday, October 23, 2023 at 8:34:28 AM UTC-4 Domenico Andreoli wrote:
>
> > Hi,
> >
> > I'm writing a Go library to embed/extend CPython (Pygolo
> > <https://gitlab.com/pygolo/py>). CPython uses thread local storage and

Bryan C. Mills

unread,
Oct 25, 2023, 10:11:47 AM10/25/23
to Domenico Andreoli, golang-nuts
On Wed, Oct 25, 2023 at 9:58 AM Domenico Andreoli <domenico...@linux.com> wrote:
On Tue, Oct 24, 2023 at 09:58:22AM -0700, 'Bryan C. Mills' via golang-nuts wrote:
> If a C thread calls into Go, the goroutine processing that call (and only
> that goroutine) will run on the C thread. The Go runtime will initialize

Is this thanks to the `lockOSThread` call in function `cgocallbackg` of
https://github.com/golang/go/blob/master/src/runtime/cgocall.go?

I'm not sure, to be honest. I think so, though. 😅

Jason E. Aten

unread,
Oct 29, 2023, 7:32:27 PM10/29/23
to golang-nuts
You could also look at the existing Go <-> Python interfaces and see how they handle such issues. Might give you hints.

https://github.com/qur/gopy. (python 3.11)

Domenico Andreoli

unread,
Oct 30, 2023, 8:25:53 AM10/30/23
to Jason E. Aten, golang-nuts
Thanks for the pointers, I'll check what they do.
Reply all
Reply to author
Forward
0 new messages