go学习笔记-goroutine的好兄弟channel

语言: CN / TW / HK

上一篇介绍了 atomic 包以及 互斥锁 mutex 来解决并发竞争状态的问题。这一篇主要来介绍 go 中与 goroutine 经常搭档的好兄弟 channel

channel 不仅可以可以来用消除竞争状态,还可以用于不同的 goroutine 中进行通信,发送与接受数据。chaanel的定义有两种,分为 有缓存无缓冲

创建channel

chan1 := make(chan int) // 创建一个无缓冲的 整形 channel
chan2 := make(chan int,2)// 创建一个有缓冲的 整形 channel

上面的代码片段,我们分别创建了一个无缓冲的 channel 与一个有缓冲的 channelchannel 的创建是使用 make(chan type,[lenght]) 来创建,如果指定了第二个参数 length 表示创建了一个长度为 length 的有缓存 channel ,反之我们称之为无缓冲。

channel的值传递

var number int
func main()  {
    chan1 := make(chan int) //创建一个无缓冲的 整形channel
    go numberAdd(chan1)
    fmt.Printf("改变之后的number:%d\r\n",<-chan1)
    //改变之后的number:1
}
func numberAdd(c chan int)  {
    number++;
    c<-number; //往chan写值
}

这里我们创建了一个整形的 channelgoroutine 中往 chan 中写值,在main函数中取值。

无缓冲与有缓存通道的区别

  • 无缓冲通道:是指在接收前没有能力保存任何值的通道。无缓冲的通道要求发送和接收的 goroutine 同时准备好才能完成发送和接收操作。如果两个 goroutine 没有同时准备好,通道会导致先执行发送或接收操作的 goroutine 阻塞等待。
  • 有缓冲通道:在在被接收前可以接受一个或多个值。有缓冲通道不要求发送与接受的 groutine 同时准备好。只有在通道中没有空间容纳新值的时候,发送动作才会发送阻塞;只有在通道中没有值要接收时,接收动作才会阻塞。
  • 区别:无缓冲通道可以保证接收跟发送数据是在同一时间,而有缓存通道则不能保证这一点。

下面来看两个例子

无缓存通道

var wait sync.WaitGroup
const  needProcessNumber = 3 //需要三次加工
func main()  {
    wait.Add(1)
    sausage := make(chan int) // 腊肠
    go processing(sausage) //开始加工程序
    sausage<-1 //开始第一次加工
    wait.Wait()
}

func processing(sausage chan int)  {
    defer wait.Done()
    for  {
        nowNumber := <-sausage
        fmt.Printf("第%d次加工开始\r\n",nowNumber)
        for i:=1; i<=10; i++ {
            fmt.Printf("%d \r\n",i*10)
        }
        fmt.Printf("第%d次加工结束\r\n",nowNumber)
        if nowNumber==needProcessNumber{
            fmt.Printf("新鲜的腊肠出炉了\r\n")
            close(sausage)
            return
        }else {
            go processing(sausage) //等待下一次加工开始
        }
        nowNumber++
        sausage <- nowNumber
        //这里会加锁直到流程交接结束
    }
}

这个例子创建了一个 Int 无缓冲通道来表示腊肠,做一个腊肠需要三次加工, main 函数中创建了一个 wait 来等待加工完成。准备一个加工的 goroutine processing 等待第一个杯子准备就绪的信号,当接收到第一个信号时,开始加工,然后等待当前加工完成,如果当前 goroutine 不是第三次加工的 goroutine ,那么准备下一个加工程序开始,进入下一个 goroutine ,直到第三次加工完成。

有缓存通道

var wait sync.WaitGroup
const (
    maxTask = 10 //最大处理工作数
    workerNumber = 3 //当前工人数
)
func main()  {
    wait.Add(workerNumber) //等到所有的work都结束
    tasks := make(chan int,maxTask)
    for workerOnline:=1;workerOnline<=workerNumber;workerOnline++ {
        go worker(tasks,workerOnline)
    }
    //增加十个需要处理的工作
    for i:=1;i<=maxTask ; i++ {
        tasks<-i
    }
    close(tasks)//所有工作完成
    wait.Wait()
}
//员工开始工作
func worker(task chan int,workNumber int)  {
    defer  wait.Done()
    for{
        taskNumber,ok := <-task
        if !ok {
            fmt.Printf("工人:%d 没有工作可以做了\r\n",workNumber)
            return
        }
        fmt.Printf("工人:%d 开始工作,当前任务编号:%d\r\n",workNumber,taskNumber)
        workTime := rand.Int63n(100)
        time.Sleep(time.Duration(workTime)*time.Millisecond)
        fmt.Printf("工人:%d 工作完成,当前任务编号:%d\r\n",workNumber,taskNumber)
    }
}

这里我们声明了一个容量为 10 的有缓冲通道 task 来表示总共有十个任务需要 3 个员工来处理。每个员工是一个 goroutine 来单独完成工作。员工首先准备就绪,然后等待任务的下发。当监听到有任务进入时,开始完成工作,直到监听到 task 通道已经关闭。需要注意的是我们在新增完10个任务时就已经关闭了 channel ,这个时候 goroutine 仍然可以从 channel 取值,直到取到的返回数值是 零值 ,如果你这个时候获取了 channel 的标志位,那么会返回一个 false ,所以我们判断 channel 是否关闭应该用这个标志位来判断。

期待一起交流

分享到: