hiddenbyte.log

2020-07-05

Digging into Goroutines I

Go enables writing programs with concurrent threads of execution. And it does so by introducing the concept of goroutine.

A goroutine has a simple model: it is a function executing concurrently with other goroutines in the same address space.

in Effective Go

Before digging into goroutines, having an understanding on “how” the Go compiler and runtime work together in order to launch a goroutine will pave the path to its internals.

Note: the examples and tools used on this document are based on Go 1.12.9 (darwin/amd64 build).

Launching a goroutine

Launching a goroutine is just as simple as calling a function and can be achieved by writing a “go” statement.

A “go” statement starts the execution of a function call as an independent concurrent thread of control, or goroutine, within the same address space.

in The Go Programming Language Specification

The source code below - main.go - has a “go” statement declared (at line 9), that launches a goroutine, executing the heavyWeightChamp function in a different thread of execution.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package main

import (
    "log"
    "time"
)

func main() {
    go heavyWeightChamp("some message")
    time.Sleep(2 * time.Second)
}

func heavyWeightChamp(message string) {
    log.Printf("heavyWeightChamp: Received message '%v'", message)
}

The Go compiler turns the “go” statement into a call to the runtime.newproc1 function. This can be verified by inspecting the application binary using objdump2 tool as depicted in the script below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
go build main.go
go tool objdump main | grep  main.go:9
# Output:
#  main.go:9    0x109659d   c7042410000000      MOVL $0x10, 0(SP)
#  main.go:9    0x10965a4   488d055da10300      LEAQ go.func.*+127(SB), AX
#  main.go:9    0x10965ab   4889442408          MOVQ AX, 0x8(SP)
#  main.go:9    0x10965b0   488d05be410300      LEAQ go.string.*+4341(SB), AX
#  main.go:9    0x10965b7   4889442410          MOVQ AX, 0x10(SP)
#  main.go:9    0x10965bc   48c74424180c000000  MOVQ $0xc, 0x18(SP)
#  main.go:9    0x10965c5   e836a9f9ff          CALL runtime.newproc(SB)

The runtime.newproc receives as function arguments:

At this point, we already know “how” the Go compiler and runtime work together to launch a goroutine: through the runtime.newproc function.

runtime.newproc

The runtime.newproc function is part of the runtime package. Also the function name is “unexported”, which means that it can only be referenced or used inside the runtime package and it only can be called indirectly through a “go” statement.

runtime.newproc is responsible for creating a goroutine, which runs the provided function, and placing it in a “waiting to run goroutines” queue.

Conclusion