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
setnsnetwork namespaces,unshare, capabilities, orsetuid. Go 1.16+ appliessyscall.Setuidto all threads. - APIs that must run on the main thread, such as macOS Cocoa. Lock in
initso thatmainstays 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
- Q159What is sysmon and what does it do?
- Q160What does runtime.Gosched do and when (if ever) should you use it?
- Q162What is a goroutine leak? Give common causes.
- Q163How do you detect and debug goroutine leaks in production and tests?
- Q164How many goroutines can a Go program run? How do you bound them?
- Q165Why doesn't Go expose goroutine IDs, and how do you "kill" a goroutine?