go语言的管道 | 青训营笔记

98 阅读3分钟

传统的并发模型主要分为 Actor模型和CSP模型,CSP 模型(communicating sequential processes)由并发执行实体(进程、线程或协程),和消息通道组成,实体之间通过消息通道发送消息进行通信。 和 Actor 模型不同,CSP 模型关注的是消息发送的载体,而不是发送消息的执行实体。Go 语言的并发模型就参考了 CSP 理论,其中执行实体对应的是go协程,消息通道指的就是channel。

管道是Go语言在语言级别上提供的goroutine间的通讯方式,我们可以使用channel在多个goroutine之间传递消息,go是不推荐使用共享内存来进行通信的。channel是进程内的通讯方式,是不支持跨进程通信的,如果需要进程间通讯的话,可以使用类似于Socket、管道、信号量等进程间通信方式,关键字是chan。 channel定义方式:

var ch chan int // 定义了一个int类型的管道ch
var ch = make(chan int) // make了一个int 类型的管道ch
var ch = make(chan int, 10)// make了一个int 类型的size为10的管道ch

channel的使用:

var ch = make(chan int, 10)
var intValue = 2
ch <- value // 传入管道
intResult <- ch // 管道输出
close(ch)  // 关闭管道

使用上来看就这么简单,那channel 有什么作用,如果有Java或者c的并发编程经验就简单的多了,我们可以使用chan的读写操作来阻塞go协程。看一个demo:

func writeChan(ch chan int, i int) {
    ch <- i
    fmt.Printf("write %v\n", i)
}

func readChan(ch chan int) {
    fmt.Printf("read: %v \n", <- ch)
    time.Sleep(1 *  time.Second)
}

func main() {
    ch := make(chan int,10)
    for i:=0; i<20; i++ {
        go writeChan(ch, i)
    }
    for i:=0; i<20; i++ {
        readChan(ch)
    }
}

这样就相当于一次性起了n个协程去做事情,main 会因为读chan阻塞,事情完成后放行,虽然可以这么用来当作协程间的协作,其实channel核心作用其实还是通信作用。

channel其实是一种有缓冲的管道。那具体来说什么是缓冲呢? 需要注意一点,根据Go语言内存模型规范,对于从无缓冲Channel进行的接收,发生在对该Channel进行的发送完成之前。 使用channel就是经典的生产者消费者模式,缓冲可以理解为channel的一种资源池,缓冲的大小就是资源池的容量,有资源时才能取资源,有空闲时才能放资源,定义方式就是上文提到:var ch = make(chan int, 10) go的channel实际上就是阻塞队列的结构: 当缓冲区为0时(无缓冲) 当channel有元素时,只能读了才能继续写, 当channel无元素时,只有写了才能读。

当缓冲区不为0时(有缓冲) 当channel有元素时,但缓冲区有空余位置,写操作不会被阻塞,缓冲区无空闲位置才会阻塞。(可以理解为同样是阻塞结构但容量是 size(缓冲区) + 1) 当channel无元素时,只有写了才能读。 鉴于这种阻塞行为,chan 使用不正确是会容易导致死锁的,比如说一个正在读,但某些原因没有进行写操作。类似的情况有不少,使用的时候一定要注意。