576 words
3 minutes
Inside Go Runtime - makechan
2025-07-21

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 選擇分配方式 :

  1. mem == 0 : 無元素 or size 為 0

    c = (*hchan)(mallocgc(hchanSize, nil, true))
    c.buf = c.raceaddr()

    由於不需要 buffer,只要一個 hchan 結構本身就好,所以 mallocgc 填入 needzero == true 進行空間與效能最佳化。

  2. !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 的位址。

  3. 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/
Author
nu1lspaxe
Published at
2025-07-21