As we know, the go keyword runs goroutines. Or rather, this is how we often think about it.
The go keyword in the Go programming language is one of its core elements, distinguishing it from other programming languages. It is used to launch a function in a separate goroutine. This allows the program to continue executing the current code without waiting for the invoked function to complete.
In this article, we will examine the exact role of the go keyword and explore how its use affects program execution.
func main() {
go func(){
fmt.Println("Hello World")
}()
os.Exit(0)
}
What does this code print? If go keyword actually runs goroutine, most times we see “Hello World” as output of our program.
But in reality the result is not determined. To figure it out, why this happens, we need to remember, what is goroutines queue in GMP model.
In this picture, we can see LRQ and GRQ. This is a local queue and global queue. Every “P” has its own local queue. “P” put “G” (which mean Goroutine) to “M” from that queue. But how goroutines get into this queue?
To explain of this moment we actually need to understand what does do go keyword. How do we do this? Of course, by disassembling our program.
GOOS=linux GOARCH=amd64 go build -gcflags='-S' -o /dev/null ./main.go &> asm.S
As result we get the following go assembly code.
main.main STEXT size=50 args=0x0 locals=0x10 funcid=0x0 align=0x0
0x0000 00000 (:5) TEXT main.main(SB), ABIInternal, $16-0
0x0000 00000 (:5) CMPQ SP, 16(R14)
0x0004 00004 (:5) PCDATA $0, $-2
0x0004 00004 (:5) JLS 43
0x0006 00006 (:5) PCDATA $0, $-1
0x0006 00006 (:5) PUSHQ BP
0x0007 00007 (:5) MOVQ SP, BP
0x000a 00010 (:5) SUBQ $8, SP
0x000e 00014 (:5) FUNCDATA $0, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)
0x000e 00014 (:5) FUNCDATA $1, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)
0x000e 00014 (:6) LEAQ main.main.func1·f(SB), AX
0x0015 00021 (:6) PCDATA $1, $0
0x0015 00021 (:6) CALL runtime.newproc(SB)
0x001a 00026 (:9) XORL AX, AX
0x001c 00028 (:9) NOP
0x0020 00032 (:9) CALL os.Exit(SB)
0x0025 00037 (:10) ADDQ $8, SP
0x0029 00041 (:10) POPQ BP
0x002a 00042 (:10) RET
0x002b 00043 (:10) NOP
0x002b 00043 (:5) PCDATA $1, $-1
0x002b 00043 (:5) PCDATA $0, $-2
0x002b 00043 (:5) CALL runtime.morestack_noctxt(SB)
0x0030 00048 (:5) PCDATA $0, $-1
0x0030 00048 (:5) JMP 0
0x0000 49 3b 66 10 76 25 55 48 89 e5 48 83 ec 08 48 8d I;f.v%UH..H...H.
0x0010 05 00 00 00 00 e8 00 00 00 00 31 c0 0f 1f 40 00 ..........1...@.
0x0020 e8 00 00 00 00 48 83 c4 08 5d c3 e8 00 00 00 00 .....H...]......
0x0030 eb ce
.....
In this assembly code, we interested two moments:
0x000e 00014 (:6) LEAQ main.main.func1·f(SB), AX
What does this code mean? We put the anonimous function func1 into AX register. This is how Linux ABI works. Linux ABI demand to passing arguments via registers.
Second interested moment is:
0x0015 00021 (:6) CALL runtime.newproc(SB)
This code call runtime.newproc function. This function answers our question: what does go keyword actually do. Lets look at its source code:
// Create a new g running fn.
// Put it on the queue of g's waiting to run.
// The compiler turns a go statement into a call to this.
func newproc(fn *funcval) {
gp := getg()
pc := getcallerpc()
systemstack(func() {
newg := newproc1(fn, gp, pc)
pp := getg().m.p.ptr()
runqput(pp, newg, true)
if mainStarted {
wakep()
}
})
}
Lets skip the unnecessary details.
newg := newproc1(fn, gp, pc)
This piece of code create new internal/runtime goroutine representation.
And finally:
runqput(pp, newg, true)
Put this goroutine to local queue
// runqput tries to put g on the local runnable queue.
// If next is false, runqput adds g to the tail of the runnable queue.
// If next is true, runqput puts g in the pp.runnext slot.
// If the run queue is full, runnext puts g on the global queue.
// Executed only by the owner P.
func runqput(pp *p, gp *g, next bool) {
....
}
And… Nothing. There’s no more about running goroutine. Lets put it to local queue.
In conclusion, the go keyword creates a goroutine and places it into a local queue. The actual execution is handled by the Go scheduler, which manages goroutines using the GMP model. Understanding this process is key to optimizing concurrency in Go.

Top comments (0)