In anticipation of the start of the Golang Developer course . Professional we invite everyone to a free demo lesson on the topic: "Integration tests in Go" .
And now we traditionally publish a useful translation.
, , - Go, , « » « ». , « 2 », , Go 1.4, , .
, , , , , Go.
Go 1.14, .
(Goroutine scheduler) (work-stealing) , Go 1.1 Go. . , , , , G, M P; , () .
«G» - Golang.
«M» - , - .
«P» ; , Go , .
, G (, ) M ( ) P ( ).
M , P P. Go , . , , G G - .
// g , stacksize .
func malg(stacksize int32) *g {
newg := new(g) // <---
if stacksize >= 0 {
stacksize = round2(_StackSystem + stacksize)
systemstack(func() {
newg.stack = stackalloc(uint32(stacksize))
})
newg.stackguard0 = newg.stack.lo + _StackGuard
newg.stackguard1 = ^uintptr(0)
...
...
}
return newg
}
// g, fn narg , argp. callerpc - go, . g g, .
func newproc1(fn *funcval, argp unsafe.Pointer, narg int32, callergp *g, callerpc uintptr) {
...
acquirem() // , p var
siz := narg
siz = (siz + 7) &^ 7
...
_p_ := _g_.m.p.ptr()
newg := gfget(_p_)
if newg == nil {
newg = malg(_StackMin) // !!! <-
casgstatus(newg, _Gidle, _Gdead)
allgadd(newg)
}
...
}
, !
70 . :
type g struct {
stack stack
stackguard0 uintptr
stackguard1 uintptr
_panic *_panic
_defer *_defer
m *m
sched gobuf
syscallsp uintptr
syscallpc uintptr
stktopsp uintptr
param unsafe.Pointer
atomicstatus uint32
stackLock uint32
goid int64
schedlink guintptr
waitsince int64
waitreason waitReason
preempt bool
preemptStop bool
preemptShrink bool
asyncSafePoint bool
paniconfault bool
gcscandone bool
throwsplit bool
activeStackChans bool
raceignore int8
sysblocktraced bool
sysexitticks int64
traceseq uint64
tracelastp puintptr
lockedm muintptr
sig uint32
writebuf []byte
sigcode0 uintptr
sigcode1 uintptr
sigpc uintptr
gopc uintptr
ancestors *[]ancestorInfo
startpc uintptr
racectx uintptr
waiting *sudog
cgoCtxt []uintptr
labels unsafe.Pointer
timer *timer
selectDone uint32
gcAssistBytes int64
}
!
; uintptr
64 , .. 8 , int64
. 1 , - .
, timer
(~70 ), _panic
(~40 ) _defer
(~100 ), 600 .
, … «2 »?
…
g
stack
.
type g struct {
// .
// stack : [stack.lo, stack.hi).
// stackguard0 - , Go.
// stackguard1 - , C.
...
stack stack // , runtime/cgo
stackguard0 uintptr // , liblink
stackguard1 uintptr // , liblink
, , .
type stack struct {
lo uintptr
hi uintptr
}
, , : « ?», , 2 !
2 , - .
, . , Go , , , ; , runtime.morestack, , . , , , .
2048 , Go ; 1 64- 250 32- .
, runtime.abort. ; , ,
package main
func foo(i int) int {
if i < 1e8 {
return foo(i + 1)
}
return -1
}
func main() {
foo(0)
}
, , runtime.abort .
$ go run exceed-stack.go
runtime: goroutine stack exceeds 1000000000-byte limit
fatal error: stack overflow
runtime stack:
runtime.throw(0x1071ce1, 0xe)
/usr/local/go/src/runtime/panic.go:774 +0x72
runtime.newstack()
/usr/local/go/src/runtime/stack.go:1046 +0x6e9
runtime.morestack()
/usr/local/go/src/runtime/asm_amd64.s:449 +0x8f
goroutine 1 [running]:
main.foo(0xffffdf, 0x0)
...
...
, ?
50 .
: ( ) .
$ ~ go run poc-goroutines-sizing.go
# 10
Number of goroutines: 100000
Per goroutine:
Memory: 2115.71 bytes
Time: 1.404500 µs
# 1
Number of goroutines: 1000000
Per goroutine:
Memory: 2655.21 bytes
Time: 1.518857 µs
# 3
Number of goroutines: 3000000
Per goroutine:
Memory: 2700.37 bytes
Time: 1.637003 µs
# 6
Number of goroutines: 6000000
Per goroutine:
Memory: 2700.29 bytes
Time: 2.541744 µs
# 9
Number of goroutines: 9000000
Per goroutine:
Memory: 2700.27 bytes
Time: 2.857699 µs
# 12
Number of goroutines: 12000000
Per goroutine:
Memory: 2694.09 bytes
Time: 3.232870 µs
# 50
Number of goroutines: 50000000
Per goroutine:
Memory: 2695.37 bytes
Time: 5.098005 µs
!
, Go . , Go, , , .
src/runtime/HACKING.md, Golang .
, - - , Go.
!
https://stackoverflow.com/questions/8509152/max-number-of-goroutines
https://medium.com/a-journey-with-go/go-how-does-the-goroutine-stack-size-evolve-447fc02085e5
https://dave.cheney.net/2013/06/02/why-is-a-goroutines-stack-infinite
https://www.ardanlabs.com/blog/2018/08/scheduling-in-go-part1.html
https://medium.com/@genchilu/if-a-goroutine-call-a-new-goroutine-which-one-would-scheduler-pick-up-first-890002dc54f8
https://povilasv.me/go-scheduler/
package main
import (
"flag"
"fmt"
"os"
"runtime"
"time"
)
var n = flag.Int("n", 3*1e6, "Number of goroutines to create")
var ch = make(chan byte)
var counter = 0
func f() {
counter++
<-ch//
}
func main() {
flag.Parse()
if *n <= 0 {
fmt.Fprintf(os.Stderr, "invalid number of goroutines")
os.Exit(1)
}
// 1
runtime.GOMAXPROCS(1)
// MemStats
var m0 runtime.MemStats
runtime.ReadMemStats(&m0)
t0 := time.Now().UnixNano()
for i := 0; i < *n; i++ {
go f()
}
runtime.Gosched()
t1 := time.Now().UnixNano()
runtime.GC()
// MemStats
var m1 runtime.MemStats
runtime.ReadMemStats(&m1)
if counter != *n {
fmt.Fprintf(os.Stderr, "failed to begin execution of all goroutines")
os.Exit(1)
}
fmt.Printf("Number of goroutines: %d\n", *n)
fmt.Printf("Per goroutine:\n")
fmt.Printf(" Memory: %.2f bytes\n", float64(m1.Sys-m0.Sys)/float64(*n))
fmt.Printf(" Time: %f µs\n", float64(t1-t0)/float64(*n)/1e3)
}