Go并发编程-Channel

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("超时")
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值