2

上一篇介绍了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是否关闭应该用这个标志位来判断。

期待一起交流

短腿子猿


旧梦发癫
678 声望76 粉丝

PHP、Golang