Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Will time.Sleep block goroutine?

Tags:

go

I have next code:

par.go

package main

import (
    "runtime";
    "time"
)

func main() {
    runtime.GOMAXPROCS(4)
    ch := make(chan int)
    n := 1
    for i := 0; i < n; i++ {
        go func() {
            time.Sleep(60 * time.Second)
            ch <- 1
        }();
    }
    for i := 0; i < n; i++ {
        <-ch
    }
}

I use next to run it:

$ go build par.go
$ time ./par

Then, confirm how many threads in this process:

$ ps -ef | grep par
shubunt+  3670 32131  0 12:35 pts/0    00:00:00 ./par
$ cat /proc/3670/status | grep -i threads
Threads:        5

You can see there are 5 threads.

If I change the value of n in code, then situations are next:

n := 100, Threads is 8
n := 10000, Threads is 9
n := 100000, Threads is 9
n := 1000000, Threads is 9
n := 2000000, Threads is 10

I know, go scheduler follow MPG model, here P = 4, so M = 4, M is 1:1 with KSE(Kernel threads). If any goroutine in any blocking status, the P will detached from current M, and find a idle M or new a M if can't find.

So, my question is: is time.Sleep really blocking goroutine? If not, why new threads there when I increase value of n from 1 to 2000000? If yes, there is 60 seconds there, why just scheduler new a little new M, I expect a lots of new threads there?

UPDATE:

Add another example from this.

test.go:

package main

import (
    "io/ioutil"
    "os"
    "runtime"
    "strconv"
)

func main() {
    runtime.GOMAXPROCS(2)
    data := make([]byte, 128*1024*1024)
    for i := 0; i < 200; i++ {
        go func(n int) {
            for {
                err := ioutil.WriteFile("testxxx"+strconv.Itoa(n), []byte(data), os.ModePerm)
                if err != nil {
                    println(err)
                    break
                }
            }
        }(i)
    }
    select {}
}

If not use Sleep, use real IO, the threads number will be 202 on my machine.

So, my question also related to the difference of above 2 examples, when I should worry about scheduler generate too many kernel threads for me?

like image 599
atline Avatar asked Mar 18 '26 19:03

atline


2 Answers

[I]s time.Sleep really blocking goroutine?

Yes.

But how goroutines are actually scheduled to threads is a) complicated, b) different in every release, c) can be different from architecture to architecture and d) is not specified by the language. While the "MPG model" is an accurate model if how the scheduler works it is just a model.

If the scheduler determines that 10 threads are enough to not run 200'000 goroutines as they all are time.Sleeping then 10 threads are enough.

Basically there is nothing to worry or think about such stuff in Go (in stark contrast to other languages where extreme care has to be devoted to such peculiarities). "Blocking" just means that the next statement cannot be executed right away as the actual statement is not finished jet. This may happen for a plethora if reasons ranging from time.Sleep which does nothing except wait, waiting form RAM, waiting for disk or waiting for network data. Handling all in the same manner would simplify the scheduler but make it a bad one. So no, time.Sleep does not block the goroutine. The problem is "block goroutine" is not something with a defined meaning. And it need not be defined as there is nothing interesting to know about it.

Update:

[W]hen I should worry about scheduler generate too many kernel threads for me?

Never.

There a two different scenarios: A) writing normal, sensible production code and B) writing handcrafted code which is carefully designed to create lots of threads which are all waiting in disk IO to finish. Of course you can deliberately trick the scheduler and the OS and come up with a pathological program which creates too much threads (this problem that the scheduler can be tricked is addresses in issue #4056) but that is not something to worry about. Just do not deliberately do stupid things.

There are lots of ways to trick your computer. Writing racy code is one way. The race detector helps identifying them. Worrying about race conditions before writing them is a good thing (as this happens). Creating too many threads can happen and you can ask your OS for the thread count. And in the unlikely event that there are too many: Fix it. But this is unlikely. Its a bit like OOM errors: It is dead simple to write code which OOMs but there is nothing to worry constantly about OOM while writing code. If your experience OOM you redesign but you do not start any trivial project by worrying about OOM and what you need to know about OOM, how to prevent it and what to do about it. Unless you know already that your data uses lots of memory. Same here: If you know that your code will do massive concurrent disk-IO and this is intrinsic to the domain then you might worry about this and handle this in code but the techniques are the same in every language.

like image 115
Volker Avatar answered Mar 20 '26 13:03

Volker


Summarize my final understanding about this question in case anyone interested:

  1. For time.Sleep, if I set to launch 20 goroutines, next is the results of delve:

     ./dlv attach 2070
     Type 'help' for list of commands.
     (dlv) threads
     * Thread 2070 at 0x44d003 /usr/lib/go-1.12/src/runtime/sys_linux_amd64.s:536 runtime.futex
       Thread 2071 at 0x44d003 /usr/lib/go-1.12/src/runtime/sys_linux_amd64.s:536 runtime.futex
       Thread 2072 at 0x44d003 /usr/lib/go-1.12/src/runtime/sys_linux_amd64.s:536 runtime.futex
       Thread 2092 at 0x44d003 /usr/lib/go-1.12/src/runtime/sys_linux_amd64.s:536 runtime.futex
       Thread 2093 at 0x44d003 /usr/lib/go-1.12/src/runtime/sys_linux_amd64.s:536 runtime.futex
     (dlv) grs
       Goroutine 1 - User: /home/shubuntu1/gotrial/new/par.go:19 main.main (0x452686)
       Goroutine 2 - User: /usr/lib/go-1.12/src/runtime/proc.go:302    runtime.gopark (0x425f3f)
       Goroutine 3 - User: /usr/lib/go-1.12/src/runtime/proc.go:302 runtime.gopark (0x425f3f)
       Goroutine 4 - User: /usr/lib/go-1.12/src/runtime/proc.go:307 time.Sleep (0x43cc69)
       Goroutine 5 - User: /usr/lib/go-1.12/src/runtime/proc.go:307 time.Sleep (0x43cc69)
       Goroutine 6 - User: /usr/lib/go-1.12/src/runtime/proc.go:307 time.Sleep (0x43cc69)
       Goroutine 7 - User: /usr/lib/go-1.12/src/runtime/proc.go:307 time.Sleep (0x43cc69)
       Goroutine 8 - User: /usr/lib/go-1.12/src/runtime/proc.go:307 time.Sleep (0x43cc69)
       Goroutine 9 - User: /usr/lib/go-1.12/src/runtime/proc.go:307 time.Sleep (0x43cc69)
       Goroutine 10 - User: /usr/lib/go-1.12/src/runtime/proc.go:307 time.Sleep (0x43cc69)
       Goroutine 11 - User: /usr/lib/go-1.12/src/runtime/proc.go:307 time.Sleep (0x43cc69)
       Goroutine 12 - User: /usr/lib/go-1.12/src/runtime/proc.go:307 time.Sleep (0x43cc69)
       Goroutine 13 - User: /usr/lib/go-1.12/src/runtime/proc.go:307 time.Sleep (0x43cc69)
       Goroutine 14 - User: /usr/lib/go-1.12/src/runtime/proc.go:307 time.Sleep (0x43cc69)
       Goroutine 15 - User: /usr/lib/go-1.12/src/runtime/proc.go:307 time.Sleep (0x43cc69)
       Goroutine 16 - User: /usr/lib/go-1.12/src/runtime/proc.go:307 time.Sleep (0x43cc69)
       Goroutine 17 - User: /usr/lib/go-1.12/src/runtime/proc.go:307 time.Sleep (0x43cc69)
       Goroutine 18 - User: /usr/lib/go-1.12/src/runtime/proc.go:307 time.Sleep (0x43cc69)
       Goroutine 19 - User: /usr/lib/go-1.12/src/runtime/proc.go:307 time.Sleep (0x43cc69)
       Goroutine 20 - User: /usr/lib/go-1.12/src/runtime/proc.go:307 time.Sleep (0x43cc69)
       Goroutine 21 - User: /usr/lib/go-1.12/src/runtime/proc.go:307 time.Sleep (0x43cc69)
       Goroutine 22 - User: /usr/lib/go-1.12/src/runtime/proc.go:307 time.Sleep (0x43cc69)
       Goroutine 23 - User: /usr/lib/go-1.12/src/runtime/proc.go:307 time.Sleep (0x43cc69)
     * Goroutine 24 - User: /usr/lib/go-1.12/src/runtime/lock_futex.go:228 runtime.notetsleepg (0x409064)
     [24 goroutines]
    

    You can see, there are many goroutines in time.Sleep while just a little threads, these threads are in process of runtime.futex.

    From Understanding Golang sleep() function and also this, we could know golang sleep will involve a lock, so this possible a reason that some additional thread launched, but the lock quickly released, so not so many threads there, many goroutines still could reuse the same thread.

  2. For ioutil.WriteFile, if I set to launch 20 goroutines, next is the results of delve:

     ./dlv attach 2455
     Type 'help' for list of commands.
     (dlv) threads
     * Thread 2455 at 0x460e90 /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall
       Thread 2456 at 0x4530f3 /usr/lib/go-1.12/src/runtime/sys_linux_amd64.s:536 runtime.futex
       Thread 2457 at 0x460f0a /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:53 syscall.Syscall6
       Thread 2458 at 0x460e90 /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall
       Thread 2459 at 0x460e90 /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall
       Thread 2460 at 0x460e90 /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall
       Thread 2461 at 0x460e90 /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall
       Thread 2462 at 0x460e90 /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall
       Thread 2463 at 0x460e90 /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall
       Thread 2464 at 0x460e90 /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall
       Thread 2465 at 0x460e90 /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall
       Thread 2466 at 0x460e90 /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall
       Thread 2468 at 0x460e90 /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall
       Thread 2469 at 0x460e90 /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall
       Thread 2470 at 0x460e90 /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall
       Thread 2471 at 0x460e90 /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall
       Thread 2472 at 0x460f0a /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:53 syscall.Syscall6
       Thread 2473 at 0x460f0a /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:53 syscall.Syscall6
       Thread 2474 at 0x460e90 /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall
       Thread 2475 at 0x460f0a /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:53 syscall.Syscall6
       Thread 2476 at 0x460f0a /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:53 syscall.Syscall6
       Thread 2477 at 0x4530f3 /usr/lib/go-1.12/src/runtime/sys_linux_amd64.s:536 runtime.futex
     (dlv) grs
       Goroutine 1 - User: /home/shubuntu1/gotrial/newnew/test.go:24 main.main (0x4754b0)
       Goroutine 2 - User: /usr/lib/go-1.12/src/runtime/proc.go:302 runtime.gopark (0x429d9f)
       Goroutine 3 - User: /usr/lib/go-1.12/src/runtime/proc.go:302 runtime.gopark (0x429d9f)
       Goroutine 4 - User: /usr/lib/go-1.12/src/runtime/proc.go:302 runtime.gopark (0x429d9f)
       Goroutine 5 - User: /usr/lib/go-1.12/src/runtime/proc.go:302 runtime.gopark (0x429d9f)
       Goroutine 6 - User: /usr/lib/go-1.12/src/runtime/proc.go:302 runtime.gopark (0x429d9f)
       Goroutine 7 - User: /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:53 syscall.Syscall6 (0x460f0a) (thread 2457)
       Goroutine 8 - User: /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:53 syscall.Syscall6 (0x460f0a) (thread 2476)
       Goroutine 9 - User: /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall (0x460e90) (thread 2461)
       Goroutine 10 - User: /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:53 syscall.Syscall6 (0x460f0a) (thread 2473)
       Goroutine 11 - User: /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall (0x460e90) (thread 2468)
       Goroutine 12 - User: /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall (0x460e90) (thread 2469)
     * Goroutine 13 - User: /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall (0x460e90) (thread 2455)
       Goroutine 14 - User: /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall (0x460e90) (thread 2459)
       Goroutine 15 - User: /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall (0x460e90) (thread 2460)
       Goroutine 16 - User: /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall (0x460e90) (thread 2471)
       Goroutine 17 - User: /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall (0x460e90) (thread 2464)
       Goroutine 18 - User: /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall (0x460e90) (thread 2462)
       Goroutine 19 - User: /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall (0x460e90) (thread 2466)
       Goroutine 20 - User: /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall (0x460e90) (thread 2474)
       Goroutine 21 - User: /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall (0x460e90) (thread 2470)
       Goroutine 22 - User: /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall (0x460e90) (thread 2463)
       Goroutine 23 - User: /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:53 syscall.Syscall6 (0x460f0a) (thread 2472)
       Goroutine 24 - User: /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall (0x460e90) (thread 2458)
       Goroutine 25 - User: /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:53 syscall.Syscall6 (0x460f0a) (thread 2475)
       Goroutine 26 - User: /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall (0x460e90) (thread 2465)
     [26 goroutines]
     (dlv)
    

    You can see, there are many goroutines in the process of syscall, while also there are many threads in syscall, not like sleep in runtime.futex which just invove lock. The ioutil involve real block syscall. This is because epoll not works for regular files on linux, so not like network io which have a network poller handle blocking interface with a non-block low level sys call, see this.

    So, I guess if we can't bear too many threads for regular file operation, then in application level, above design not reasonable. Maybe we need a series of dedicated file operation goroutines, and all file related requirements should be delegated to these limited number of dedicated goroutines with channel.

like image 28
atline Avatar answered Mar 20 '26 14:03

atline