go: runtime: let idle OS threads exit
- What version of Go are you using (
go version
)? 1.5.3, 1.6 - What operating system and processor architecture are you using (
go env
)? x86_64 - OSX and Linux - What did you do? Any golang program will create a new OS thread when it needs to if things are blocked. But these threads aren’t ever destroyed. For example, a program using 7 goroutines might have 40+ OS threads hanging around. The numbers will surely get much higher as traffic fluctuates against a golang server process throughout the day.
- What did you expect to see? Once an OS thread has been idle long enough, I would have expected it to be destroyed. Being recreated if needed. Expanding and contracting like the relationship between the heap and GC.
- What did you see instead? The many OS threads that were created hang around even with an idle program and very few goroutines.
After doing some reading into other (closed) issues on this repo dating back to 2012 - including the one where SetMaxThreads
was introduced - I’m curious, why keep the OS threads around instead of cleaning them up?
About this issue
- Original URL
- State: open
- Created 8 years ago
- Reactions: 5
- Comments: 26 (14 by maintainers)
Commits related to this issue
- runtime: make it possible to exit Go-created threads Currently, threads created by the runtime exist until the whole program exits. For #14592 and #20395, we want to be able to exit and clean up thre... — committed to golang/go by aclements 7 years ago
- added thread count to runtime stats. go never closes OS threads once opened, see: https://github.com/golang/go/issues/14592 — committed to simonmittag/j8a by simonmittag 2 years ago
- Merge pull request #250 from simonmittag/242 added thread count to runtime stats. go never closes OS threads once opened, see: https://github.com/golang/go/issues/14592 — committed to simonmittag/j8a by simonmittag 2 years ago
maybe we could reduce M number by using
runtime.LockOSThread()
, and here is a demo to illustrate how it works.when a LockOSThread goroutine exit without calling UnLockOSThread, the corresponding M will exit.
Any news regarding this?
In my particular case I am developing for IoT hardware running Linux that only has 1 vCPU and 128 MB of memory. From my testing I am estimating that a single thread in my case has a 80 KiB memory overhead (which is strange in itself since a Ubuntu 20.04 LTS amd64 server I also tested on approx. has an overhead of only 8 KiB). My program after running for a week uses on average 40 goroutines at any given time, however the thread count after a week is 220+. This would mean the remaining threads (if each goroutine is scheduled on it’s own thread) memory overhead is over 14 MiB (
220 - 40 = 180 threads * 80 KiB ~= 14 MiB
)! I know that doesn’t seem like a lot however on these memory constrained devices that run Linux it’s still an unnecessary resource hog.I would be perfectly happy if there was a function that we could call in the runtime package to trigger a GC-like thread cleanup.
@superajun-wsj Please add text as text, not as an image. Actual text is much easier to read than text in an image. Thanks.
Is this fixed in some go version?
Hi @superajun-wsj Not sure that it is good solution. When the child process is created by one thread called A with
PdeathSignal: SIGKILL
and the thread A becomes idle, if the thread A exits, the child process will receive KILL signal. So I think UnLockOSThread might introduce other issues. just my two cents.Any update on this?
I just discovered that exiting threads might affect IO. From WSASendTo documentation https://msdn.microsoft.com/en-us/library/windows/desktop/ms741693(v=vs.85).aspx - “… Note All I/O initiated by a given thread is canceled when that thread exits. For overlapped sockets, pending asynchronous operations can fail if the thread is closed before the operations complete. See ExitThread for more information. …”
Alex
Issue #22439 has an example of a problem caused by not exiting idle Ms (and freeing their stack memory).
This is certainly technically possible, but the devil’s in the details. For example, we often manipulate pointers to Ms (the structure representing an OS thread) in contexts that don’t interact with stop-the-world and can’t have write barriers, which means it’s difficult to know when we’re actually done with an M and can safely recycle it. Even if we don’t release the M structure itself and just release the OS thread, we have to know when we can safely reuse the M for another OS thread to avoid races.