Goroutine

A running Go program is composed of one or more goroutines, and each goroutine can be considered as an independent task. Goroutine and thread have many commonalities, such as: every goroutine(thread) has its private stack and registers; if the main goroutine(thread) exits, the program will exit, and so on. But on modern Operating System (E.g., Linux), the actual execution and scheduled unit is thread, so if a goroutine wants to become running, it must “attach” to a thread. Let’s see an example:

  1. package main
  2. import (
  3. "time"
  4. )
  5. func main() {
  6. time.Sleep(1000 * time.Second)
  7. }

What the program does is just sleeping for a while, not does anything useful. After launching it on Linux, use Delve to attach the running process and observe the details of it:

  1. (dlv) threads
  2. * Thread 1040 at 0x451f73 /usr/local/go/src/runtime/sys_linux_amd64.s:307 runtime.futex
  3. Thread 1041 at 0x451f73 /usr/local/go/src/runtime/sys_linux_amd64.s:307 runtime.futex
  4. Thread 1042 at 0x451f73 /usr/local/go/src/runtime/sys_linux_amd64.s:307 runtime.futex
  5. Thread 1043 at 0x451f73 /usr/local/go/src/runtime/sys_linux_amd64.s:307 runtime.futex
  6. Thread 1044 at 0x451f73 /usr/local/go/src/runtime/sys_linux_amd64.s:307 runtime.futex

We can see there are 5 threads of this process, let’s confirm it by checking /proc/1040/task/ directory:

  1. # cd /proc/1040/task/
  2. # ls
  3. 1040 1041 1042 1043 1044

Yeah, the thread information of Delve is right! Check the particulars of goroutines:

  1. (dlv) goroutines
  2. [4 goroutines]
  3. Goroutine 1 - User: /usr/local/go/src/runtime/time.go:59 time.Sleep (0x43e236)
  4. Goroutine 2 - User: /usr/local/go/src/runtime/proc.go:263 runtime.gopark (0x426f73)
  5. Goroutine 3 - User: /usr/local/go/src/runtime/proc.go:263 runtime.gopark (0x426f73)
  6. * Goroutine 4 - User: /usr/local/go/src/runtime/lock_futex.go:206 runtime.notetsleepg (0x40b1ce)

There is only one main goroutine, what the hell of the other 3 goroutines? Actually, the other 3 goroutines are system goroutines, and you can refer related info here. The number of main goroutine is 1, and you can inspect it:

  1. (dlv) goroutine 1
  2. Switched from 4 to 1 (thread 1040)
  3. (dlv) bt
  4. 0 0x0000000000426f73 in runtime.gopark
  5. at /usr/local/go/src/runtime/proc.go:263
  6. 1 0x0000000000426ff3 in runtime.goparkunlock
  7. at /usr/local/go/src/runtime/proc.go:268
  8. 2 0x000000000043e236 in time.Sleep
  9. at /usr/local/go/src/runtime/time.go:59
  10. 3 0x0000000000401013 in main.main
  11. at ./gocode/src/goroutine.go:8
  12. 4 0x0000000000426b9b in runtime.main
  13. at /usr/local/go/src/runtime/proc.go:188
  14. 5 0x0000000000451000 in runtime.goexit
  15. at /usr/local/go/src/runtime/asm_amd64.s:1998

Using go keyword can create and start a goroutine, see another case:

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. func main() {
  7. ch := make(chan int)
  8. go func(chan int) {
  9. var count int
  10. for {
  11. count++
  12. ch <- count
  13. time.Sleep(10 * time.Second)
  14. }
  15. }(ch)
  16. for v := range ch {
  17. fmt.Println(v)
  18. }
  19. }

The go func statement spawns another goroutine which works as a producer; while the main goroutine behaves as a consumer. And the output should be:

  1. 1
  2. 2
  3. ......

Use Delve to check the goroutine aspects:

  1. (dlv) goroutines
  2. [6 goroutines]
  3. Goroutine 1 - User: ./gocode/src/goroutine.go:20 main.main (0x40106c)
  4. Goroutine 2 - User: /usr/local/go/src/runtime/proc.go:263 runtime.gopark (0x429fc3)
  5. Goroutine 3 - User: /usr/local/go/src/runtime/proc.go:263 runtime.gopark (0x429fc3)
  6. Goroutine 4 - User: /usr/local/go/src/runtime/proc.go:263 runtime.gopark (0x429fc3)
  7. Goroutine 5 - User: /usr/local/go/src/runtime/time.go:59 time.Sleep (0x442ab6)
  8. * Goroutine 6 - User: /usr/local/go/src/runtime/lock_futex.go:206 runtime.notetsleepg (0x40cf4e)
  9. (dlv) goroutine 1
  10. Switched from 6 to 1 (thread 1997)
  11. (dlv) bt
  12. 0 0x0000000000429fc3 in runtime.gopark
  13. at /usr/local/go/src/runtime/proc.go:263
  14. 1 0x000000000042a043 in runtime.goparkunlock
  15. at /usr/local/go/src/runtime/proc.go:268
  16. 2 0x00000000004047eb in runtime.chanrecv
  17. at /usr/local/go/src/runtime/chan.go:470
  18. 3 0x0000000000404354 in runtime.chanrecv2
  19. at /usr/local/go/src/runtime/chan.go:360
  20. 4 0x000000000040106c in main.main
  21. at ./gocode/src/goroutine.go:20
  22. 5 0x0000000000429beb in runtime.main
  23. at /usr/local/go/src/runtime/proc.go:188
  24. 6 0x0000000000455de0 in runtime.goexit
  25. at /usr/local/go/src/runtime/asm_amd64.s:1998
  26. (dlv) goroutine 5
  27. Switched from 1 to 5 (thread 1997)
  28. (dlv) bt
  29. 0 0x0000000000429fc3 in runtime.gopark
  30. at /usr/local/go/src/runtime/proc.go:263
  31. 1 0x000000000042a043 in runtime.goparkunlock
  32. at /usr/local/go/src/runtime/proc.go:268
  33. 2 0x0000000000442ab6 in time.Sleep
  34. at /usr/local/go/src/runtime/time.go:59
  35. 3 0x00000000004011d6 in main.main.func1
  36. at ./gocode/src/goroutine.go:16
  37. 4 0x0000000000455de0 in runtime.goexit
  38. at /usr/local/go/src/runtime/asm_amd64.s:1998

The number of main goroutine is 1, whilst func is 5.

Another caveat you should pay attention to is the switch point among goroutines. It can be blocking system call, channel operations, etc.

Reference:
Effective Go;
Performance without the event loop;
How Goroutines Work.