Go 协程通信实现(下)—— 通过 channel 进行消息传递
channel

通道是 Go 语言在语言级别提供的协程通信方式,它是一种数据类型,本身是并发安全的,我们可以使用它在多个 goroutine 之间传递消息,而不必担心通道中的值被污染。

注:需要注意的是,通道是进程内的通信方式,因此通过通道传递对象的过程和调用函数时的参数传递行为一致,也可以传递指针。如果需要跨进程通信,建议通过分布式系统的方法来解决,比如使用 Socket 或者 HTTP 等通信协议,Go 语言对于网络方面也有非常完善的支持,学院君会在介绍完并发编程后介绍网络通信。

makechan
ch := make(chan int)
chint
<-
ch <- 1  // 表示把元素 1 发送到通道 ch

接收时通道变量在右,可以通过指定变量接收元素值:

element := <-ch

也可以留空表示忽略:

<-ch

这样一来,通过箭头指向我们就可以清楚的判断是写入数据到通道还是从通道读取数据,非常简单形象。

channel.go
package main

import (
    "fmt"
    "time"
)

func add(a, b int, ch chan int) {
    c := a + b
    fmt.Printf("%d + %d = %d\n", a, b, c)
    ch <- 1
}


func main() {
    start := time.Now()
    chs := make([]chan int, 10)
    for i := 0; i < 10; i++ {
        chs[i] = make(chan int)
        go add(1, i, chs[i])
    }
    for _, ch := range chs {
        <- ch
    }
    end := time.Now()
    consume := end.Sub(start).Seconds()
    fmt.Println("程序执行耗时(s):", consume)
}
chsadd()ch <- 1<-chchs

之所以上述这段代码可以实现和「共享内存+锁」一样的效果,是因为往通道写入数据和从通道接收数据都是原子操作,或者说是同步阻塞的,当我们向某个通道写入数据时,就相当于该通道被加锁,直到写入操作完成才能执行从该通道读取数据的操作,反过来,当我们从某个通道读取数据时,其他协程也不能操作该通道,直到读取完成,如果通道中没有数据,则会阻塞在这里,直到通道被写入数据。因此,可以看到通道的发送和接收操作是互斥的,同一时间同一个进程内的所有协程对某个通道只能执行发送或接收操作,两者不可能同时进行,这样就保证了并发的安全性,数据不可能被污染。

main()

程序耗时和共享内存相当,但是代码要简洁的多,优雅的多。当然,本篇教程只是介绍了通道的最基本使用,接下来,我将花几个教程的篇幅为大家系统介绍通道,并结合协程(goroutine)+通道(channel)为大家展现 Go 并发编程的魅力。