什么是channel?
go语言采用的是CSP(通信顺序进程)并发模型,实现了其中的Process/Channel理论(对应go语言中的goroutine/channel),channel在go语言中作为多线程中的数据共享实现,通常与goroutine搭配进行多线程通信。
channel是否线程安全?
安全。channel设计用于多线程之间的通信,因为必须保证线程安全性。
打开方式:
//初始化方式
var ch1 = make(chan int) //无缓冲
var ch2 = make(chan int, 1) //有1缓冲
//for range 方式读取ch1
go func() {
for num := range ch1 {
fmt.Println(num)
}
fmt.Println("ch1 close")
}()
//for select方式读取ch2
go func() {
Loop:
for {
select {
case num, ok := <-ch2:
if ok {
fmt.Println(num)
} else {
fmt.Println("ch2 close")
break Loop
}
}
}
}()
ch1 <- 1
ch2 <- 2
time.Sleep(1 * time.Second)
close(ch1)
close(ch2)
time.Sleep(1 * time.Second)
/**
输出:
1
2
ch2 close
ch1 close
*/
channel原理:
channel的实现可以在 rutime/chan.go中找到代码实现。
type hchan struct {
qcount uint // 队列数据个数
dataqsiz uint // 环形队列大小
buf unsafe.Pointer // 存放实际数据的指针 unsafe.Pointer设计上避免GC
elemsize uint16 // 元素类型大小
closed uint32 // channel关闭标识
elemtype *_type // 类型元数据
sendx uint // 发送数据 index
recvx uint // 接收数据 index
recvq waitq // 接收数据链表
sendq waitq // 发送数据链表
// 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 shrinki
lock mutex //锁
}
type waitq struct {
first *sudog
last *sudog
}
channel主要基于hchan结构实现,可以发现,其本质是环形队列实现。
发送数据时,通过调用chansend方法,实现逻辑如下:
//加锁,保证原子实现
lock(&c.lock)
//判断channel关闭则无法操作,panc
if c.closed != 0 {
unlock(&c.lock)
panic(plainError("send on closed channel"))
}
//接收队列有等待协程,则直接把数据复制给等待协程实现
if sg := c.recvq.dequeue(); sg != nil {
// Found a waiting receiver. We pass the value we want to send
// directly to the receiver, bypassing the channel buffer (if any).
send(c, sg, ep, func() { unlock(&c.lock) }, 3)
return true
}
//channel有缓冲,且未满则把发送数据放入缓冲区,sendx下标后移
if c.qcount < c.dataqsiz {
// Space is available in the channel buffer. Enqueue the element to send.
qp := chanbuf(c, c.sendx)
if raceenabled {
racenotify(c, c.sendx, nil)
}
typedmemmove(c.elemtype, qp, ep)
c.sendx++
if c.sendx == c.dataqsiz {
c.sendx = 0
}
c.qcount++
unlock(&c.lock)
return true
}
接收数据时,通过调用chanrecv方法,实现逻辑如下:
//加锁
lock(&c.lock)
if c.closed != 0 {
//channel处于关闭状态,缓冲区依然有数据,可以继续执行
if c.qcount == 0 {
if raceenabled {
raceacquire(c.raceaddr())
}
unlock(&c.lock)
if ep != nil {
typedmemclr(c.elemtype, ep)
}
return true, false
}
// The channel has been closed, but the channel's buffer have data.
} else {
//发送队列中还有等待协程,则继续处理
// Just found waiting sender with not closed.
if sg := c.sendq.dequeue(); sg != nil {
// Found a waiting sender. If buffer is size 0, receive value
// directly from sender. Otherwise, receive from head of queue
// and add sender's value to the tail of the queue (both map to
// the same buffer slot because the queue is full).
recv(c, sg, ep, func() { unlock(&c.lock) }, 3)
return true, true
}
}
//缓冲区还有数据则copy出来处理
if c.qcount > 0 {
// Receive directly from queue
qp := chanbuf(c, c.recvx)
if raceenabled {
racenotify(c, c.recvx, nil)
}
if ep != nil {
typedmemmove(c.elemtype, ep, qp)
}
typedmemclr(c.elemtype, qp)
c.recvx++
if c.recvx == c.dataqsiz {
c.recvx = 0
}
c.qcount--
unlock(&c.lock)
return true, true
}