每一個協程都須要有本身的棧空間,來存放變量,函數,寄存器等信息。因此係統須要給協程分配足夠的棧空間。java
每一個協程都有相同的,固定大小的棧。c++
優勢:實現簡單;golang
缺點:每一個協程須要的棧空間不盡相同,若是一律而論,那麼有些是浪費,有些是不夠用。bash
由開發者在建立時指定協程棧大小。java, c++在建立線程時能夠指定其棧大小。less
優勢:實現簡單函數
缺點:對開發者要求比較高,須要根據棧變量,請求量預估。可是有些場景不太好預估,好比遞歸調用,這種狀況一般只能往大的估計。優化
分配和釋法額外的內存空間。初始分配的比較小的空間,如4k。不夠了再增長,用完即釋放。如下是一個例子: ui
當G調用H的時候,沒有足夠的棧空間來讓H運行,這時候Go運行環境就會從堆裏分配一個新的棧內存塊去讓H運行。在H返回到G以前,新分配的內存塊被釋放回堆。這種管理棧的方法通常都工做得很好。但對有些代碼,特別是遞歸調用,它會形成程序不停地分配和釋放新的內存空間。舉個例子,在一個程序裏,函數G會在一個循環裏調用不少次H函數。每次調用都會分配一塊新的內存空間。這就是熱分裂問題(hot split problem)。優勢:動態擴展,初始成本小,能夠將協程看成廉價資源使用。this
缺點:存在熱分裂問題(hot split problem)。spa
動態擴展,分配更大的內存,作指針遷移。
優勢:動態擴展,初始成本小,能夠將協程看成廉價資源使用,且不存在hot split problem問題缺點:因爲一般以2倍擴展,當請求量密集,內存敏感的狀況下,內存會消耗比較多,容易oom,固然,一般的業務量是ok的,不會有任何問題。同時100w鏈接纔要考慮優化。
1.3以前採用的是Segmented stacks的方式。以後採用的Stack copying,也叫continuous stack(連續棧)
運行時,發現棧不夠用了
func newstack() {
thisg := getg()
......
gp := thisg.m.curg
......
// Allocate a bigger segment and move the stack.
oldsize := gp.stack.hi - gp.stack.lo
newsize := oldsize * 2 // 比原來大一倍
......
// The goroutine must be executing in order to call newstack,
// so it must be Grunning (or Gscanrunning).
casgstatus(gp, _Grunning, _Gcopystack) //修改協程狀態
// The concurrent GC will not scan the stack while we are doing
// the copy since the gp is in a Gcopystack status.
copystack(gp, newsize, true) //在下面會講到
......
casgstatus(gp, _Gcopystack, _Grunning)
gogo(&gp.sched)
}
複製代碼
gc進行時,非運行中協程,棧使用不超過1/4的,會縮容爲原來1/2
func shrinkstack(gp *g) {
gstatus := readgstatus(gp)
if gstatus&^_Gscan == _Gdead {
if gp.stack.lo != 0 {
// Free whole stack - it will get reallocated
// if G is used again.
stackfree(gp.stack)
gp.stack.lo = 0
gp.stack.hi = 0
}
return
}
......
oldsize := gp.stack.hi - gp.stack.lo
newsize := oldsize / 2 // 比原來小1倍
if newsize < _FixedStack {
return
}
// Compute how much of the stack is currently in use and only
// shrink the stack if gp is using less than a quarter of its
// current stack. The currently used stack includes everything
// down to the SP plus the stack guard space that ensures
// there's room for nosplit functions. avail := gp.stack.hi - gp.stack.lo //當已使用的棧佔不到總棧的1/4 進行縮容 if used := gp.stack.hi - gp.sched.sp + _StackLimit; used >= avail/4 { return } copystack(gp, newsize, false) //在下面會講到 } 複製代碼
原來內容上的拷貝
func copystack(gp *g, newsize uintptr, sync bool) {
......
old := gp.stack
......
used := old.hi - gp.sched.sp
// allocate new stack
new := stackalloc(uint32(newsize))
......
// Compute adjustment.
var adjinfo adjustinfo
adjinfo.old = old
adjinfo.delta = new.hi - old.hi //用於舊棧指針的調整
//後面有機會和 select / chan 一塊兒分析
// Adjust sudogs, synchronizing with channel ops if necessary.
ncopy := used
if sync {
adjustsudogs(gp, &adjinfo)
} else {
......
adjinfo.sghi = findsghi(gp, old)
// Synchronize with channel ops and copy the part of
// the stack they may interact with.
ncopy -= syncadjustsudogs(gp, used, &adjinfo)
}
//把舊棧數據複製到新棧
// Copy the stack (or the rest of it) to the new location
memmove(unsafe.Pointer(new.hi-ncopy), unsafe.Pointer(old.hi-ncopy), ncopy)
// Adjust remaining structures that have pointers into stacks.
// We have to do most of these before we traceback the new
// stack because gentraceback uses them.
adjustctxt(gp, &adjinfo)
adjustdefers(gp, &adjinfo)
adjustpanics(gp, &adjinfo)
......
// Swap out old stack for new one
gp.stack = new
gp.stackguard0 = new.lo + _StackGuard // NOTE: might clobber a preempt request
gp.sched.sp = new.hi - used
gp.stktopsp += adjinfo.delta
// Adjust pointers in the new stack.
gentraceback(^uintptr(0), ^uintptr(0), 0, gp, 0, nil, 0x7fffffff, adjustframe, noescape(unsafe.Pointer(&adjinfo)), 0)
......
//釋放舊棧
stackfree(old)
}
複製代碼
package main
func myFunction(a, b int) (int, int) {
return a + b, a - b
}
func main() {
myFunction(66, 77)
}
複製代碼
gentraceback裏回調了adjustframe函數,咱們所須要瞭解的即golang的棧空間中,有存放函數參數,返回值,函數返回地址等信息,這些地址都須要調節,該函數就是針對原來的棧指針進行的調節。代碼以下:
// Note: the argument/return area is adjusted by the callee.
func adjustframe(frame *stkframe, arg unsafe.Pointer) bool {
adjinfo := (*adjustinfo)(arg)
targetpc := frame.continpc
if targetpc == 0 {
// Frame is dead.
return true
}
f := frame.fn
.........
pcdata := pcdatavalue(f, _PCDATA_StackMapIndex, targetpc, &adjinfo.cache)
if pcdata == -1 {
pcdata = 0 // in prologue
}
// Adjust local variables if stack frame has been allocated.
size := frame.varp - frame.sp
var minsize uintptr
switch sys.ArchFamily {
case sys.ARM64:
minsize = sys.SpAlign
default:
minsize = sys.MinFrameSize
}
if size > minsize {
var bv bitvector
stackmap := (*stackmap)(funcdata(f, _FUNCDATA_LocalsPointerMaps))
if stackmap == nil || stackmap.n <= 0 {
print("runtime: frame ", funcname(f), " untyped locals ", hex(frame.varp-size), "+", hex(size), "\n")
throw("missing stackmap")
}
// Locals bitmap information, scan just the pointers in locals.
if pcdata < 0 || pcdata >= stackmap.n {
print("runtime: pcdata is ", pcdata, " and ", stackmap.n, " locals stack map entries for ", funcname(f), " (targetpc=", targetpc, ")\n")
throw("bad symbol table")
}
bv = stackmapdata(stackmap, pcdata)
size = uintptr(bv.n) * sys.PtrSize
if stackDebug >= 3 {
print(" locals ", pcdata, "/", stackmap.n, " ", size/sys.PtrSize, " words ", bv.bytedata, "\n")
}
adjustpointers(unsafe.Pointer(frame.varp-size), &bv, adjinfo, f)
}
// Adjust saved base pointer if there is one.
if sys.ArchFamily == sys.AMD64 && frame.argp-frame.varp == 2*sys.RegSize {
if !framepointer_enabled {
print("runtime: found space for saved base pointer, but no framepointer experiment\n")
print("argp=", hex(frame.argp), " varp=", hex(frame.varp), "\n")
throw("bad frame layout")
}
if stackDebug >= 3 {
print(" saved bp\n")
}
if debugCheckBP {
// Frame pointers should always point to the next higher frame on
// the Go stack (or be nil, for the top frame on the stack).
bp := *(*uintptr)(unsafe.Pointer(frame.varp))
if bp != 0 && (bp < adjinfo.old.lo || bp >= adjinfo.old.hi) {
println("runtime: found invalid frame pointer")
print("bp=", hex(bp), " min=", hex(adjinfo.old.lo), " max=", hex(adjinfo.old.hi), "\n")
throw("bad frame pointer")
}
}
adjustpointer(adjinfo, unsafe.Pointer(frame.varp))
}
// Adjust arguments.
if frame.arglen > 0 {
var bv bitvector
if frame.argmap != nil {
bv = *frame.argmap
} else {
stackmap := (*stackmap)(funcdata(f, _FUNCDATA_ArgsPointerMaps))
if stackmap == nil || stackmap.n <= 0 {
print("runtime: frame ", funcname(f), " untyped args ", frame.argp, "+", frame.arglen, "\n")
throw("missing stackmap")
}
if pcdata < 0 || pcdata >= stackmap.n {
print("runtime: pcdata is ", pcdata, " and ", stackmap.n, " args stack map entries for ", funcname(f), " (targetpc=", targetpc, ")\n")
throw("bad symbol table")
}
bv = stackmapdata(stackmap, pcdata)
}
if stackDebug >= 3 {
print("args\n")
}
adjustpointers(unsafe.Pointer(frame.argp), &bv, adjinfo, funcInfo{})
}
return true
}
複製代碼