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:

package main

import (
    "time"
)

func main() {
    time.Sleep(1000 * time.Second)
}

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:

(dlv) threads
* Thread 1040 at 0x451f73 /usr/local/go/src/runtime/sys_linux_amd64.s:307 runtime.futex
  Thread 1041 at 0x451f73 /usr/local/go/src/runtime/sys_linux_amd64.s:307 runtime.futex
  Thread 1042 at 0x451f73 /usr/local/go/src/runtime/sys_linux_amd64.s:307 runtime.futex
  Thread 1043 at 0x451f73 /usr/local/go/src/runtime/sys_linux_amd64.s:307 runtime.futex
  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:

# cd /proc/1040/task/
# ls
1040  1041  1042  1043  1044

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

(dlv) goroutines
[4 goroutines]
  Goroutine 1 - User: /usr/local/go/src/runtime/time.go:59 time.Sleep (0x43e236)
  Goroutine 2 - User: /usr/local/go/src/runtime/proc.go:263 runtime.gopark (0x426f73)
  Goroutine 3 - User: /usr/local/go/src/runtime/proc.go:263 runtime.gopark (0x426f73)
* 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:

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

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

package main

import (
    "fmt"
    "time"
)

func main() {
    ch := make(chan int)

    go func(chan int) {
        var count int
        for {
            count++
            ch <- count
            time.Sleep(10 * time.Second)
        }
    }(ch)

    for v := range ch {
        fmt.Println(v)
    }
}

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
2
......

Use Delve to check the goroutine aspects:

(dlv) goroutines
[6 goroutines]
  Goroutine 1 - User: ./gocode/src/goroutine.go:20 main.main (0x40106c)
  Goroutine 2 - User: /usr/local/go/src/runtime/proc.go:263 runtime.gopark (0x429fc3)
  Goroutine 3 - User: /usr/local/go/src/runtime/proc.go:263 runtime.gopark (0x429fc3)
  Goroutine 4 - User: /usr/local/go/src/runtime/proc.go:263 runtime.gopark (0x429fc3)
  Goroutine 5 - User: /usr/local/go/src/runtime/time.go:59 time.Sleep (0x442ab6)
* Goroutine 6 - User: /usr/local/go/src/runtime/lock_futex.go:206 runtime.notetsleepg (0x40cf4e)
(dlv) goroutine 1
Switched from 6 to 1 (thread 1997)
(dlv) bt
0  0x0000000000429fc3 in runtime.gopark
   at /usr/local/go/src/runtime/proc.go:263
1  0x000000000042a043 in runtime.goparkunlock
   at /usr/local/go/src/runtime/proc.go:268
2  0x00000000004047eb in runtime.chanrecv
   at /usr/local/go/src/runtime/chan.go:470
3  0x0000000000404354 in runtime.chanrecv2
   at /usr/local/go/src/runtime/chan.go:360
4  0x000000000040106c in main.main
   at ./gocode/src/goroutine.go:20
5  0x0000000000429beb in runtime.main
   at /usr/local/go/src/runtime/proc.go:188
6  0x0000000000455de0 in runtime.goexit
   at /usr/local/go/src/runtime/asm_amd64.s:1998
(dlv) goroutine 5
Switched from 1 to 5 (thread 1997)
(dlv) bt
0  0x0000000000429fc3 in runtime.gopark
   at /usr/local/go/src/runtime/proc.go:263
1  0x000000000042a043 in runtime.goparkunlock
   at /usr/local/go/src/runtime/proc.go:268
2  0x0000000000442ab6 in time.Sleep
   at /usr/local/go/src/runtime/time.go:59
3  0x00000000004011d6 in main.main.func1
   at ./gocode/src/goroutine.go:16
4  0x0000000000455de0 in runtime.goexit
   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.

results matching ""

    No results matching ""