2018-11-06 golang 中Channel用法笔记

栏目: Go · 发布时间: 6年前

内容简介:本来觉得自己的golang的channel已经有所了解了,但实际在被问一些问题的时候,还是没法回答的非常明确,所以今天又梳理整理了一遍,并且把容易出错的问题记录一下。废话不多说,先上一段代码:想一下输出结果应该是什么呢?我承认这个一开始确实自己没有看仔细,

本来觉得自己的golang的channel已经有所了解了,但实际在被问一些问题的时候,还是没法回答的非常明确,所以今天又梳理整理了一遍,并且把容易出错的问题记录一下。

废话不多说,先上一段代码:

package main

import (
    "fmt"
)

func Count(ch chan int, n int) {
    fmt.Println("Counting ", n)
    ch <- n
}

func main() {
    chs := make([]chan int, 10)
    for i := 0; i < 10; i++ {
        chs[i] = make(chan int)
        go Count(chs[i], i)
    }

    for _, ch := range chs {
        v := <-ch
        fmt.Println("v  ", v)
    }
}

想一下输出结果应该是什么呢?我承认这个一开始确实自己没有看仔细, chs 是一个信道数组,他的每一个参数都是重新new的一个信道,所以输出结果应该是这样:

Counting  9
Counting  0
v   0
Counting  1
v   1
Counting  6
... 
全部会输出,并且顺序不一定

我一开始想,这个信道不是没有缓存吗?应该读一个取一个才对,但是没看到,其实数组里面的每一个参数都是重新new的信道。那什么情况才会是我想象中的输出结果呢?代码可以改成这样,只改main函数就可以:

func main() {
    ch := make(chan int)
    for i := 0; i < 10; i++ {
        go Count(ch, i)
    }

    for i := 0; i < 10; i++ {
        v := <-ch
        fmt.Println("v  ", v)
    }
}

这个时候,就是读一个取一个,因为只有一个信道,输出结果:

Counting  9
v   9
Counting  0
v   0
Counting  1
v   1
Counting  2
v   2
...

这个时候,我想用一个带缓存的信道进行存储,然后用range进行读取,代码就改成了这样:

func Count(ch chan int, n int) {
    fmt.Println("Counting ", n)
    ch <- n

}

func main() {
    ch := make(chan int, 10) //改成有缓存的信道
    for i := 0; i < 10; i++ {
        go Count(ch, i)
    }

    for v := range ch {
        fmt.Println("v  ", v)
    }
}

结果输出的时候报错了:

...
Counting  8
v   8
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.main()
        /home/tfd/workspace/go/src/test/main.go:24 +0x126

死锁了,原来是因为 range 不会检测信道是否干涸(drained,也就是信道是否已经读取完),在读取完所有数据后,再次读取,会导致main函数挂起。那怎么办呢,第一种方法,如果我知道一共缓存了多少数据,那我就读多少数据就可以了,代码如下:

func main() {
    ch := make(chan int, 10)
    for i := 0; i < 10; i++ {
        go Count(ch, i)
    }

    for i := 0; i < 10; i++ { //这边假设我知道一共存了10个数据,那就读10次就好了,如果读11次,那就会报deadlock错误
        v := <-ch
        fmt.Println("v  ", v)
    }
}

读取结果如下:

Counting  9
v   9
Counting  0
v   0
Counting  1
v   1
Counting  2
v   2
Counting  3
v   3
Counting  7
v   7
Counting  6
v   6
Counting  8
v   8
Counting  4
Counting  5
v   4
v   5

那实际现实中,我很大可能不知道一共写入了多少次,那怎么办呢?我们可以用 sync.WaitGroup , 简单介绍下 WaitGroup 对象内部有一个计数器,最初从0开始,它一共包含三个方法: Add() , Done() , Wait() 用来控制计数器的数量。 Add(n) 把计数器设置为 nDone() 每次把计数器 -1Wait() 会阻塞代码的运行,直到计数器的值减为0. 那我们用 WaitGroup 改一下代码就可以了。

func Count(ch chan int, n int, wg *sync.WaitGroup) {
    defer wg.Done()
    fmt.Println("Counting ", n)
    ch <- n
}

func main() {
    //wg := sync.WaitGroup{} //这里可以用new(sync.WaitGroup),这样直接就可以得到指针类型的WaitGroup
      wg:= new(sync.WaitGroup)
        ch := make(chan int, 10)

    for i := 0; i < 10; i++ {
        wg.Add(1)
        go Count(ch, i, wg)
    }

    go func() {
        wg.Wait()
        close(ch)
        fmt.Println("Finish")
    }()

    for v := range ch {
        fmt.Println("v:", v)
    }
}

这时,代码运行就没问题了,输出结果

Counting  5
Counting  4
v: 5
v: 4
Counting  2
v: 2
Counting  6
Counting  1
v: 6
Counting  0
Counting  8
Counting  3
v: 1
v: 0
v: 8
v: 3
Counting  7
v: 7
Counting  9
Finish
v: 9

但是在这里我也是遇到坑的,一开始 WaitGroup 没有传指针,代码是这样的:

func Count(ch chan int, n int, wg sync.WaitGroup) { //wg没有传指针
    defer wg.Done()
    fmt.Println("Counting ", n)
    ch <- n
}

func main() {
    wg := sync.WaitGroup{}
    ch := make(chan int, 10)

    for i := 0; i < 10; i++ {
        wg.Add(1)
        go Count(ch, i, wg) //wg没有传指针
    }

    go func() {
        wg.Wait()
        close(ch)
        fmt.Println("Finish")
    }()

    for v := range ch {
        fmt.Println("v:", v)
    }
}

这个时候,代码仍然报deadlock,结果我就不贴了。这是因为这个时候wg是将拷贝传递到了goroutine中,所以,wg只有Add操作,而Done操作是在wg的副本中执行的,因此Wait就死锁了。正确的应该是要传指针,上一段代码已经把正确的写进去了。

这里还可以注意一点就是,信道 close 了之后可以读但是不可以写,并且, close 之后的信道是不会阻塞的,所以才可以通过 WaitGroup 的方法在数据写完之后把信道 close 就不会出现阻塞的情况了。


以上所述就是小编给大家介绍的《2018-11-06 golang 中Channel用法笔记》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

QBasic语言程序设计教程(第2版习题解答)

QBasic语言程序设计教程(第2版习题解答)

刘瑞新、丁爱萍 / 电子工业出版社 / 1999-6-1 / 13.00

本书是《QBasic语言程序设计教程》(第二版)一书的配套教材、本书第一部分以概要的形式,对全书进行了总结,以便学生复习。在第二部分中,对《QBasic语言程序设计教程》(第二版)中的习题做了详尽的分析与解答。 本书也可作为QBasic语言的习题研单独使用。一起来看看 《QBasic语言程序设计教程(第2版习题解答)》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试