576 words
3 minutes
Inside Go Runtime - makechan
Source code
// go/src/runtime/chan.go
const (
maxAlign = 8
hchanSize = unsafe.Sizeof(hchan{}) + uintptr(-int(unsafe.Sizeof(hchan{}))&(maxAlign-1))
debugChan = false
)
func makechan(t *chantype, size int) *hchan {
elem := t.Elem
// compiler checks this but be safe.
if elem.Size_ >= 1 << 16 {
throw("makechan: invalid channel element type")
}
if hchanSize%maxAlign != 0 || elem.Align_ > maxAlign {
throw("makechan: bad alignment")
}
mem, overflow := math.MulUintptr(elem.Size_, uintptr(size))
if overflow || mem > maxAlloc-hchanSize || size < 0 {
panic(plainError("makechan: size out of range"))
}
// Hchan does not contain pointers interesting for GC when elements stored in buf
// do not contain pointers.
// buf points into the same allocation, elemtype is persistent.
// SudoG's are referenced from their owning thread so they can't be collected.
// TODO(dvyukov,rlh): Rethink when collector can move allocated objects.
var c *hchan
switch {
case mem == 0:
// Queue or element size is zero.
c = (*hchan)(mallocgc(hchanSize, nil, true))
// Race detector uses this location for synchronization.
c.buf = c.raceaddr()
case !elem.Pointers():
// Elements do not contain pointers.
// Allocate hchan and buf in one call.
c = (*hchan)(mallocgc(hchanSize+mem, nil, true))
c.buf = add(unsafe.Pointer(c), hchanSize)
default:
// Elements contain pointers.
c = new(hchan)
c.buf = mallocgc(mem, elem, true)
}
c.elemsize = uint16(elem.Size_)
c.elemtype = elem
c.dataqsiz = uint(size)
if b := getg().bubble; b != nil {
c.bubble = b
}
lockInit(&.lock, lockRankHchan)
if debugChan {
print("makechan: chan=", c, "; elemsize=", elem.Size_, "; dataqsiz=", size, "\n")
}
return c
}
// go/src/runtime/malloc.go
//
// func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer
首先看到 makechan
的參數,
t
: channel 的型別size
: buffer 的大小,如果為 0 則代表它是 unbuffered channel
回傳值是 channel 的 pointer (*hchan
)。所以記得我們之前文章提到過的
Don’t communicate by sharing memory; share memory by communicating
句中的 no shared memory 不包含 hchan
。
進入函數後,會依序檢查 elem.Size_
(channel 元素的大小,不超過 64KB)、memory alignment、再來是記憶體大小與 overflow 計算 (元素大小 x channel 容量 = 記憶體需求)。
接下來才是根據元素是否包含 pointer 選擇分配方式 :
mem == 0
: 無元素 or size 為 0c = (*hchan)(mallocgc(hchanSize, nil, true)) c.buf = c.raceaddr()
由於不需要 buffer,只要一個
hchan
結構本身就好,所以mallocgc
填入needzero == true
進行空間與效能最佳化。!elem.Pointers()
: 元素不含 pointer (e.g.,int
,float
)c = (*hchan)(mallocgc(hchanSize+mem, nil, true)) c.buf = add(unsafe.Pointer(c), hchanSize)
如果不包含 pointer 的話,GC 就不需要追蹤 buffer,所以可以透過
hchanSize+mem
一次性分配記憶體,然後再讓c.buf
指向記憶體中 offset 為hchanSize
的位址。default
: 含有 pointer (e.g.,string
,chan
,map
)c = new(hchan) c.buf = mallocgc(mem, elem, true)
為了讓 GC 追蹤 buffer 中的元素 (記憶體區塊),必須單獨配置
c.buf
,並告知mallocgc
對應的型別資訊。
最後再用 lockInit
保護 channel 共享資源。
Inside Go Runtime - makechan
https://nu1lspaxe.github.io/posts/20250721/