Go并发编程-Channel
Channel在Go中是first-class,其地位是非常重要的。在golang中,channel的使用是非常简单的。在Golang中流传一条有名的话
Don’t communicate by sharing memory, share memory by communicating.
在业务的goroutine中,不要使用共享内存的方式通信,而是使用channel进行通信。
简单用
channel使用make进行初始化,channel分为三种类型,双向channel,只能发送的channel,和只能接收的channel。
并且channel又分为有buffer的channel和无buffer的channel。无buffer的channel,只有读写双方都准备好才能不阻塞。而有buffer的channel,在不满的情况下是可以继续向channel发送数据的。如果buffer中还有数据接收者也不会阻塞
func TestChannel(t *testing.T) {
ch := make(chan int)
go func() {
ch <- 1
}()
i := <-ch
fmt.Println(i)
}
看实现
channel的组成
type hchan struct {
qcount uint // total data in the queue在队列中元素的个数
dataqsiz uint // size of the circular queue队列的容量
buf unsafe.Pointer // points to an array of dataqsiz elements指向buffer的指针
elemsize uint16 //元素的大小
closed uint32 //是否关闭
elemtype *_type // element type// 元素的类型
sendx uint // send index //发送者在队列的下标
recvx uint // receive index //接受者在队列的下标
recvq waitq // list of recv waiters //接收者队列
sendq waitq // list of send waiters // 发送者队列
// 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 //数据锁
}
channel的初始化
func makechan(t *chantype, size int) *hchan {
elem := t.elem
//略去检查代码
var c *hchan
switch {
case mem == 0:
// Queue or element size is zero.无缓存的buffer,不需要初始化buffer
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.元素不是指针,分配一块连续的内存给hchan数据结构和buf
// 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 debugChan {
print("makechan: chan=", c, "; elemsize=", elem.size, "; dataqsiz=", size, "\n")
}
return c
}
chanenel发送数据
// 第一部分
//当channel为nil的时候,会永久的阻塞住
if c == nil {
if !block {
return false
}
gopark(nil, nil, waitReasonChanSendNilChan, traceEvGoStop, 2)
throw("unreachable")
}
// 第二部分
//当channel没有关闭,或者buffer满了,直接返回
//但是在chansend1调用chansend的时候block固定传的是true。所以第二部分的代码不会执行
if !block && c.closed == 0 && ((c.dataqsiz == 0 && c.recvq.first == nil) ||
(c.dataqsiz > 0 && c.qcount == c.dataqsiz)) {
return false
}
// 第三部分
//开始加锁,已经关闭的channel,发送数据的时候会直接panic
lock(&c.lock)
if c.closed != 0 {
unlock(&c.lock)
panic(plainError("send on closed channel"))
}
// 第四部分
//如果等待队列中有等待着,则不进入buffer,直接把值给等待着
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
}
// 第五部分
//如果等待队列中没有等待者,则将数据放入buffer
if c.qcount < c.dataqsiz {
// Space is available in the channel buffer. Enqueue the element to send.
qp := chanbuf(c, c.sendx)
if raceenabled {
raceacquire(qp)
racerelease(qp)
}
typedmemmove(c.elemtype, qp, ep)
c.sendx++
if c.sendx == c.dataqsiz {
c.sendx = 0
}
c.qcount++
unlock(&c.lock)
return true
}
channel接收数据
// chanrecv 也是默认传递的block=true,所以为false的情况不考虑,也是分阶段来分析下channel
//第一部分
//与发送数据相同,当channel为nil的时候,会阻塞住
if c == nil {
if !block {
return
}
gopark(nil, nil, waitReasonChanReceiveNilChan, traceEvGoStop, 2)
throw("unreachable")
}
// 第二部分
//如果channel没有关闭,并且队列中没有缓存的元素直接返回
lock(&c.lock)
if c.closed != 0 && c.qcount == 0 {
if raceenabled {
raceacquire(c.raceaddr())
}
unlock(&c.lock)
if ep != nil {
typedmemclr(c.elemtype, ep)
}
return true, false
}
// 第三部分
// 如果发送者队列中有值,并且buffer中没有值则直接从发送者队列去值给接受者。如果buffer中有值,则优先从buffer中去值
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
}
// 第四部分
//没有发送队列没值,并且buffer中有值。则从buffer中取出一个元素
if c.qcount > 0 {
// Receive directly from queue
qp := chanbuf(c, c.recvx)
if raceenabled {
raceacquire(qp)
racerelease(qp)
}
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
}
// 第五部分
// buffer中没值,则一直阻塞
...
channel 关闭
//关闭nil的channel会panic
if c == nil {
panic(plainError("close of nil channel"))
}
//关闭已经关闭的channel会panic
lock(&c.lock)
if c.closed != 0 {
unlock(&c.lock)
panic(plainError("close of closed channel"))
}
//移除channel中的reader,和writer
// release all readers
for {
sg := c.recvq.dequeue()
if sg == nil {
break
}
if sg.elem != nil {
typedmemclr(c.elemtype, sg.elem)
sg.elem = nil
}
if sg.releasetime != 0 {
sg.releasetime = cputicks()
}
gp := sg.g
gp.param = nil
if raceenabled {
raceacquireg(gp, c.raceaddr())
}
glist.push(gp)
}
// release all writers (they will panic)
for {
sg := c.sendq.dequeue()
if sg == nil {
break
}
sg.elem = nil
if sg.releasetime != 0 {
sg.releasetime = cputicks()
}
gp := sg.g
gp.param = nil
if raceenabled {
raceacquireg(gp, c.raceaddr())
}
glist.push(gp)
}
unlock(&c.lock)
别踩坑
通过上面的源码分析,可以看出有很多场景下会panic。这些panic就是你要避免的
1. clone 为nil的channel
2. send 已经close的channel
3. close 已经close的channel
**4. goroutine泄漏 **
// 无buffer的channel,会在reader和writer都准备好才不会阻塞,这里的goroutine就会阻塞住了,就是泄漏了。
//可以将ch 改成容量为1的channel
func TestChannel(t *testing.T) {
ch := make(chan int)
ch = nil
go func() {
time.Sleep(2 * time.Second)
ch <- 1
}()
select {
case res := <-ch:
fmt.Println(res)
case <-time.After(time.Second):
fmt.Println("超时")
}
}