在Go中最常見的就是通信順序進程(Communicating sequential processes,CSP)的并發模型,通過共享通信,來實現共享內存,這里就提到了channel.
Goroutine 和 Channel 分別對應 CSP 中的實體和傳遞信息的媒介,Go 語言中的 Goroutine 會通過 Channel 傳遞數據。
[](https://github.com/KeKe-Li/data-structures-questions/blob/master/src/images/139.jpg)
Goroutine通過使用channel傳遞數據,一個會向 Channel 中發送數據,另一個會從 Channel 中接收數據,它們兩者能夠獨立運行并不存在直接關聯,但是能通過 Channel 間接完成通信。
Channel 收發操作均遵循了先入先出(FIFO)的設計,具體規則如下:
* 先從 Channel 讀取數據的 Goroutine 會先接收到數據;
* 先向 Channel 發送數據的 Goroutine 會得到先發送數據的權利;
Channel 通常會有以下三種類型:
* 同步 Channel — 不需要緩沖區,發送方會直接將數據交給(Handoff)接收方;
* 異步 Channel — 基于環形緩存的傳統生產者消費者模型;
* `chan struct{}`類型的異步`Channel`的`struct{}`類型不占用內存空間,不需要實現緩沖區和直接發送(Handoff)的語義;
Channel 在運行時使用`runtime.hchan`結構體表示:
~~~go
type hchan struct {
qcount uint // 當前隊列里還剩余元素個數
dataqsiz uint // 環形隊列長度,即緩沖區的大小,即make(chan T,N) 中的N
buf unsafe.Pointer // 環形隊列指針
elemsize uint16 // 每個元素的大小
closed uint32 // 標識當前通道是否處于關閉狀態,創建通道后,該字段設置0,即打開通道;通道調用close將其設置為1,通道關閉
elemtype *_type // 元素類型,用于數據傳遞過程中的賦值
sendx uint // 環形緩沖區的狀態字段,它只是緩沖區的當前索引-支持數組,它可以從中發送數據
recvx uint // 環形緩沖區的狀態字段,它只是緩沖區當前索引-支持數組,它可以從中接受數據
recvq waitq // 等待讀消息的goroutine隊列
sendq waitq // 等待寫消息的goroutine隊列
// lock protects all fields in hchan, as well as several
// fields in sudogs blocked on this channel.
//
// Do not change another G's status while holding this lock
// (in particular, do not ready a G), as this can deadlock
// with stack shrinking.
lock mutex // 互斥鎖,為每個讀寫操作鎖定通道,因為發送和接受必須是互斥操作
}
type waitq struct {
first *sudog
last *sudog
}
~~~
其中hchan結構體中有五個字段是構建底層的循環隊列:
~~~go
* qcount — Channel 中的元素個數;
* dataqsiz — Channel 中的循環隊列的長度;
* buf — Channel 的緩沖區數據指針;
* sendx — Channel 的發送操作處理到的位置;
* recvx — Channel 的接收操作處理到的位置;
~~~
通常,`elemsize`和`elemtype`分別表示當前 Channel 能夠收發的元素類型和大小.
`sendq`和`recvq`存儲了當前 Channel 由于緩沖區空間不足而阻塞的 Goroutine 列表,這些等待隊列使用雙向鏈表`runtime.waitq`表示,鏈表中所有的元素都是`runtime.sudog`結構.
`waitq`表示一個在等待列表中的 Goroutine,該結構體中存儲了阻塞的相關信息以及兩個分別指向前后`runtime.sudog`的指針。
channel 在Go中是通過make關鍵字創建,編譯器會將make(chan int,10).
創建管道:
`runtime.makechan`和`runtime.makechan64`會根據傳入的參數類型和緩沖區大小創建一個新的 Channel 結構,其中后者用于處理緩沖區大小大于 2 的 32 次方的情況.
這里我們來詳細看下`makechan`函數:
~~~go
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.ptrdata == 0:
// 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)
lockInit(&c.lock, lockRankHchan)
if debugChan {
print("makechan: chan=", c, "; elemsize=", elem.size, "; dataqsiz=", size, "\n")
}
return c
}
~~~
Channel 中根據收發元素的類型和緩沖區的大小初始化`runtime.hchan`結構體和緩沖區:
[](https://github.com/KeKe-Li/data-structures-questions/blob/master/src/images/134.jpg)
arena區域就是我們所謂的堆區,Go動態分配的內存都是在這個區域,它把內存分割成8KB大小的頁,一些頁組合起來稱為mspan。
bitmap區域標識arena區域哪些地址保存了對象,并且用4bit標志位表示對象是否包含指針、GC標記信息。bitmap中一個byte大小的內存對應arena區域中4個指針大小(指針大小為 8B )的內存,所以bitmap區域的大小是512GB/(4\*8B)=16GB。
[](https://github.com/KeKe-Li/data-structures-questions/blob/master/src/images/135.jpg)
[](https://github.com/KeKe-Li/data-structures-questions/blob/master/src/images/136.jpg)
此外我們還可以看到bitmap的高地址部分指向arena區域的低地址部分,這里bitmap的地址是由高地址向低地址增長的。
spans區域存放mspan(是一些arena分割的頁組合起來的內存管理基本單元,后文會再講)的指針,每個指針對應一頁,所以spans區域的大小就是512GB/8KB\*8B=512MB。
除以8KB是計算arena區域的頁數,而最后乘以8是計算spans區域所有指針的大小。創建mspan的時候,按頁填充對應的spans區域,在回收object時,根據地址很容易就能找到它所屬的mspan。
- Golang基礎
- Go中new與make的區別
- Golang中除了加Mutex鎖以外還有哪些方式安全讀寫共享變量
- 無緩沖Chan的發送和接收是否同步
- Golang并發機制以及它所使用的CSP并發模型.
- Golang中常用的并發模型
- Go中對nil的Slice和空Slice的處理是一致的嗎
- 協程和線程和進程的區別
- Golang的內存模型中為什么小對象多了會造成GC壓力
- Go中數據競爭問題怎么解決
- 什么是channel,為什么它可以做到線程安全
- Golang垃圾回收算法
- GC的觸發條件
- Go的GPM如何調度
- 并發編程概念是什么
- Go語言的棧空間管理是怎么樣的
- Goroutine和Channel的作用分別是什么
- 怎么查看Goroutine的數量
- Go中的鎖有哪些
- 怎么限制Goroutine的數量
- Channel是同步的還是異步的
- Goroutine和線程的區別
- Go的Struct能不能比較
- Go的defer原理是什么
- Go的select可以用于什么
- Context包的用途是什么
- Go主協程如何等其余協程完再操作
- Go的Slice如何擴容
- Go中的map如何實現順序讀取
- Go中CAS是怎么回事
- Go中的逃逸分析是什么
- Go值接收者和指針接收者的區別
- Go的對象在內存中是怎樣分配的
- 棧的內存是怎么分配的
- 堆內存管理怎么分配的
- 在Go函數中為什么會發生內存泄露
- G0的作用
- Go中的鎖如何實現
- Go中的channel的實現
- 棧的內存是怎么分配的2
- 堆內存管理怎么分配的2
- Go中的map的實現
- Go中的http包的實現原理
- Goroutine發生了泄漏如何檢測
- Go函數返回局部變量的指針是否安全
- Go中兩個Nil可能不相等嗎
- Goroutine和KernelThread之間是什么關系
- 為何GPM調度要有P
- 如何在goroutine執行一半就退出協程
- Mysql基礎
- Mysql索引用的是什么算法
- Mysql事務的基本要素
- Mysql的存儲引擎
- Mysql事務隔離級別
- Mysql高可用方案有哪些
- Mysql中utf8和utf8mb4區別
- Mysql中樂觀鎖和悲觀鎖區別
- Mysql索引主要是哪些
- Mysql聯合索引最左匹配原則
- 聚簇索引和非聚簇索引區別
- 如何查詢一個字段是否命中了索引
- Mysql中查詢數據什么情況下不會命中索引
- Mysql中的MVCC是什么
- Mvcc和Redolog和Undolog以及Binlog有什么不同
- Mysql讀寫分離以及主從同步
- InnoDB的關鍵特性
- Mysql如何保證一致性和持久性
- 為什么選擇B+樹作為索引結構
- InnoDB的行鎖模式
- 哈希(hash)比樹(tree)更快,索引結構為什么要設計成樹型
- 為什么索引的key長度不能太長
- Mysql的數據如何恢復到任意時間點
- Mysql為什么加了索引可以加快查詢
- Explain命令有什么用
- Redis基礎
- Redis的數據結構及使用場景
- Redis持久化的幾種方式
- Redis的LRU具體實現
- 單線程的Redis為什么快
- Redis的數據過期策略
- 如何解決Redis緩存雪崩問題
- 如何解決Redis緩存穿透問題
- Redis并發競爭key如何解決
- Redis的主從模式和哨兵模式和集群模式區別
- Redis有序集合zset底層怎么實現的
- 跳表的查詢過程是怎么樣的,查詢和插入的時間復雜度
- 網絡協議基礎
- TCP和UDP有什么區別
- TCP中三次握手和四次揮手
- TCP的LISTEN狀態是什么
- 常見的HTTP狀態碼有哪些
- 301和302有什么區別
- 504和500有什么區別
- HTTPS和HTTP有什么區別
- Quic有什么優點相比Http2
- Grpc的優缺點
- Get和Post區別
- Unicode和ASCII以及Utf8的區別
- Cookie與Session異同
- Client如何實現長連接
- Http1和Http2和Grpc之間的區別是什么
- Tcp中的拆包和粘包是怎么回事
- TFO的原理是什么
- TIME_WAIT的作用
- 網絡的性能指標有哪些