go Channel详解|Go主题月

1,050 阅读3分钟

Channel

Channel作为Go的核心数据结构和Goroutine之间的通信方式,Channel是Go高并发变编程模型的重要组成部分。通常chan关键字会与range或者select组合使用。

设计原理:

在java或者其它的一些语言中,在编写多线程的代码时,最被人诟病的写法就是:通过共享内存的方式进行通信。通常对于这一类的代码,我们建议通过通信的方式共享内存,例如java中我们可以让线程阻塞来获取互斥锁。但是在Go的语言设计时,就与上诉的模型有所偏差。

Goroutine 和 Channel 分别对应 CSP 中的实体和传递信息的媒介,Goroutine 之间会通过 Channel 传递数据

在Go中,虽然也和java或者其他语言一样使用互斥锁来进行通信,但是Go提供了另外一种并发模型,通信顺序进程(Communicating sequential processes,CSP)。可以让Goroutine之间使用Channel传递数据。

特点:

  • 多个chan之间独立运行,互不关联
  • 先进先出。先从 Channel 读取数据的 Goroutine 会先接收到数据;先向 Channel 发送数据的 Goroutine 会得到先发送数据的权利;从某些意义上来说,chan实际上是一个用于同步和通信的有锁队列。

这是chan的数据结构

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
	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
}
  • qcount — Channel 中的元素个数;
  • dataqsiz — Channel 中的循环队列的长度;
  • buf — Channel 的缓冲区数据指针;
  • sendx — Channel 的发送操作处理到的位置;
  • recvx — Channel 的接收操作处理到的位置;

chan的基本操作

新建chan

 //无缓冲管道
 c1 := make(chan int)
 //缓冲管道
 c2 := make(chan int, 10)

缓冲管道

  • 无缓冲管道:会阻塞
  • 缓冲管道:不会阻塞,直到管道达到缓冲大小

发送数据

c := make(chan int, 10)
c <- 1

接收数据

c := make(chan int, 10)
c <- 1

for v := range c {
    fmt.Println(v)
}
// or 
for {
        select {
        case <-c:
                fmt.Println("done")
                return
        default:
		fmt.Println("c waiting")
        }
}

关闭通道

c := make(chan int, 10)
close(c)

注意事项:

  • 往一个已经被close的channel中继续发送数据会导致run-time panic

  • nil channel中发送数据会一致被阻塞着

  • 从一个nil channel中接收数据会一直被block

  • 从一个被closechannel中接收数据不会被阻塞,而是立即返回,接收完已发送的数据后会返回元素类型的零值(0)

  • 当使用select的时候,如果同时有多个协程同时返回,那么这时候会随机取一个协程触发case里的逻辑

chan的实际应用

在我的gin框架的系统文章中,写过一个利用chan来做一个先留中间件的代码,连接在这:

这里就不再重复赘述.

如果还有任何问题或者想了解的内容,可以关注我的公众号超级英雄吉姆,在公众号留言,我看到后第一时间回复。