Go

What does runtime.LockOSThread do and when is it needed?

Question 161HardGo 1.22 to 1.25

LockOSThread ties the calling goroutine to its current M. That goroutine then always runs on that thread, and no other goroutine runs on it. The call nests: you need the same number of UnlockOSThread calls to release it.

You need it when state lives on the thread:

  • C libraries that use thread-local storage or thread affinity (OpenGL, some GUI toolkits, some cgo libraries).
  • Linux per-thread state such as setns network namespaces, unshare, capabilities, or setuid. Go 1.16+ applies syscall.Setuid to all threads.
  • APIs that must run on the main thread, such as macOS Cocoa. Lock in init so that main stays on thread 0.
func init() { runtime.LockOSThread() } // keep main on the main thread

func inNamespace(fn func() error) error {
	errc := make(chan error, 1)
	go func() { // dedicated goroutine that exits when done
		runtime.LockOSThread()
		// No Unlock on purpose: we tainted the thread (setns), and
		// exiting while locked makes the runtime kill the thread.
		errc <- fn()
	}()
	return <-errc
}

Gotcha: since Go 1.10, if a goroutine exits while still locked, its thread is terminated instead of being returned to the pool. This is the safe way to get rid of a thread whose state you changed. A locked goroutine that blocks also costs a whole thread.

More on Goroutines & the Scheduler

All 35 Goroutines & the Scheduler questions