channel | 青训营

47 阅读1分钟

一、channel

1.1 并发通信模型

并发通信模型有两种常见的:共享数据和消息。共享数据是指多个并发单元分别保持对同一个数据的引用,最常见的就是共享内存。 当多个goroutine访问共享数据时,就涉及到锁了。

互斥锁:

var (  
    counter int  
    wg sync.WaitGroup  
    mutex sync.Mutex  
)  
  
func main() {  
    wg.Add(2)  
    go incCounter(1)  
    go incCounter(2)  
  
    wg.Wait()  
    fmt.Println(counter)  
  
}  
func incCounter(id int) {  
    defer wg.Done()  
    mutex.Lock()  
    {  
        value := counter  
        runtime.Gosched()  
        value++  
        counter = value  
    }  
    mutex.Unlock()  
}

WaitGroup一共有三个方法。add(int)设置子任务的数量,Done方法将waitGroup计数减一,表示完成一个子任务。Wait方法用于阻塞调用者,直到 WaitGroup 的计数值为0,即所有子任务都完成。

Go语言提供了消息机制作为通信方式。不同线程间通过消息来通信。go提供的消息通信机制成为channel。channel是进程内的通信方式,跨进程通信推荐分布式系统,如http通信协议。

channel 是类型相关的,也就是说,一个 channel 只能传递一种类型的值,这个类型需要在声明 channel 时指定。

1.2 创建通道


ch1 := make(chan int)                 // 创建一个整型类型的通道
ch2 := make(chan interface{})         // 创建一个空接口类型的通道, 可以存放任意格式

type Equip struct{ /* 一些字段 */ }
ch2 := make(chan *Equip)             // 创建Equip指针类型的通道, 可以存放*Equip

1.3 发送数据

通道变量  <- 值
  • 示例
// 创建一个空接口通道
ch := make(chan interface{})
// 将0放入通道中
ch <- 0
// 将hello字符串放入通道中
ch <- "hello"

注意在发数据的时候,如果一直没有接收方接收消息,那发送操作将持续阻塞。所有的goroutine(包括main)都处于等待状态

image.png

1.4 接收数据

首先,通道的收发数据在两个不同的goroutine之间进行。通道的数据在没有接收方处理时,数据发送方会持续阻塞,因此通道的接收必定在另外一个 goroutine 中进行。

接收将持续阻塞直到发送方发送数据。如果接收方接收时,通道中没有发送方发送数据,接收方也会发生阻塞,直到发送方发送数据为止。

  1. 阻塞接受数据

    data := <- ch
    

    执行该语句时将会阻塞,直到接收到数据并赋值给 data 变量。

  2. 非阻塞接受数据

    data,ok := <- ch
    

    data:表示接收到的数据。未接收到数据时,data 为通道类型的零值。

    ok:表示是否接收到数据。

    非阻塞的通道接收方法可能造成高的 CPU 占用,因此使用非常少。如果需要实现接收超时检测,可以配合 select 和计时器 channel 进行

  3. 接受并忽略数据

    <- ch
    

    执行该语句时将会发生阻塞,直到接收到数据,但接收到的数据会被忽略。

可以通过通道在 goroutine 间阻塞收发实现并发同步。


 package main

 import (
     "fmt"
 )

 func main() {

     // 构建一个通道
     ch := make(chan int)

     // 开启一个并发匿名函数
     go func() {

         fmt.Println("start goroutine")

         // 通过通道通知main的goroutine
         ch <- 0

         fmt.Println("exit goroutine")

     }()

     fmt.Println("wait goroutine")

     // 等待匿名goroutine
     <-ch

     fmt.Println("all done")
 }
  1. 循环接收

for data := range ch {

}

示例:

package main

import (
    "fmt"

    "time"
)

func main() {

    // 构建一个通道
    ch := make(chan int)

    // 开启一个并发匿名函数
    go func() {

        // 从3循环到0
        for i := 3; i >= 0; i-- {

            // 发送3到0之间的数值
            ch <- i

            // 每次发送完时等待
            time.Sleep(time.Second)
        }

    }()

    // 遍历接收通道数据
    for data := range ch {

        // 打印通道数据
        fmt.Println(data)

        // 当遇到数据0时, 退出接收循环
        if data == 0 {
                break
        }
    }

}