39

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

 4 years ago
source link: https://studygolang.com/articles/25994
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

期待一起交流

emmuEnE.jpg!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK