Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sleep should not use one thread per running call #1644

Closed
snaury opened this issue Mar 27, 2011 · 5 comments
Closed

Sleep should not use one thread per running call #1644

snaury opened this issue Mar 27, 2011 · 5 comments

Comments

@snaury
Copy link
Contributor

snaury commented Mar 27, 2011

When there are many goroutines that are likely to block on syscalls new OS threads are
spawned uncontrollably, which can lead to whole program going down if new thread cannot
be created anymore. For example, on Mac OS X 10.6 go fails to create 132nd thread. There
needs to be a way to limit maximum number of OS threads that go can create to doesn't
get out of control.

The attached patch introduces GOMAXTHREADS environment variable that limits maximum
number of threads. The minimum OS threads that simple go programs need appears to be 2
(that's why this minimum is hardcoded), to successfully complete the test suite
GOMAXTHREADS needs to be at least 3. If not specified or 0 (default) then the behavior
doesn't change and means "unlimited".

The patch is very simple and not very efficient. For example it drains sched queue and
if there are many scheduled goroutines, but small number of OS threads, constant
rescheduling is likely a performance problem, but I just didn't want to change existing
functions too much, and it's more proof of concept.

There's also a problem when runtime·iscgo is true it leads to runtime·throw("all
goroutines are asleep - deadlock!"), I don't know why, so when runtime·iscgo is
true my patch simply uses unlimited number of OS threads.

A program that can illustrate the problem is very simple:

package main

import (
    "fmt"
    "time"
)

func main() {
  for i := 0; i < 256; i++ {
    go func(i int) {
      fmt.Printf("Sleeping in %d\n", i)
      time.Sleep(60*1e9)
    }(i)
    <- time.After(100*1e6)
  }
}

Attachments:

  1. go-maxthreads.diff (2231 bytes)
@snaury
Copy link
Contributor Author

snaury commented Mar 27, 2011

Comment 1:

Oops, sorry, the correct patch is here.

Attachments:

  1. go-maxthreads.diff (2412 bytes)

@rsc
Copy link
Contributor

rsc commented Mar 27, 2011

Comment 2:

GOMAXTHREADS will not happen.  We need fewer environment
variables, not more.
This is basically working as intended: if you block 132 threads
in system calls then Go will create a 133'rd so that it can keep
doing useful work.  If there were a limit the program would just
hang mysteriously, which is just as bad as crashing, if not worse.
The real problem is that time.Sleep should not create a new
thread for each sleep.  This has been discussed on the list before.

Owner changed to @rsc.

Status changed to Accepted.

@snaury
Copy link
Contributor Author

snaury commented Mar 27, 2011

Comment 3:

I totally agree now, the real problem was Go allocating ~1TB of virtual memory per OS
thread (although I think it was actually exponential, since stack address was going
higher and higher) which meant it was running out of memory and couldn't create
additional threads. See
https://groups.google.com/d/msg/golang-nuts/tSDpLubyVDo/7fBbYVE4IxsJ for details.
The real hard-limit on number of threads on OS X is 2560, which is big enough for all
practical reasons.
Of course non-blocking time.Sleep would be awesome. :)

@gopherbot
Copy link

Comment 4 by webmaster@webmaster.ms:

It is almost done on Windows.
The idiomatic solution of asynchronous timers (used, for example, in boost::asio) is to
specify the next timer's remaining time as the GetQueuedCompletionStatus() timeout.
Return from GetQueuedCompletionStatus() wakes up a goroutine which had been waited
either for an IO completion or for a timer event.
There is already an IOCP thread in src\pkg\net\fd_windows.go with infinite timeout, and
it seems trivial to reuse it for the timing purposes.
As far as I can remember, epoll can be used the similar way.

@rsc
Copy link
Contributor

rsc commented Nov 9, 2011

Comment 5:

This issue was closed by revision 3b86026.

Status changed to Fixed.

@golang golang locked and limited conversation to collaborators Jun 24, 2016
@rsc rsc removed their assignment Jun 22, 2022
This issue was closed.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

3 participants