脚本之家,脚本语言编程技术及教程分享平台!
分类导航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|shell|

服务器之家 - 脚本之家 - Golang - golang channel读取数据的几种情况

golang channel读取数据的几种情况

2023-03-16 13:57-_-void Golang

本文主要介绍了golang channel读取数据的几种情况,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

用var定义channel且不make

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
wg := sync.WaitGroup{}
var ch chan string
 
read := func() {
    fmt.Println("reading")
    s := <-ch
    fmt.Println("read:", s)
    wg.Done()
}
 
write := func() {
    fmt.Println("writing")
    s := "t"
    ch <- s
    fmt.Println("write:", s)
    wg.Done()
}
 
wg.Add(2)
go read()
go write()
 
fmt.Println("waiting")
wg.Wait()

输出:

waiting
writing
reading
fatal error: all goroutines are asleep - deadlock!

这种情况并不是报错空指针,而是死锁。加上make看看

用var定义channel且make

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
wg := sync.WaitGroup{}
var ch = make(chan string)
 
read := func() {
    fmt.Println("reading")
    s := <-ch
    fmt.Println("read:", s)
    wg.Done()
}
 
write := func() {
    fmt.Println("writing")
    s := "t"
    ch <- s
    fmt.Println("write:", s)
    wg.Done()
}
 
wg.Add(2)
go read()
go write()

输出

waiting
writing
reading
read: t
write: t

这种情况没什么毛病,之所以先输出的read,是因为IO机制。下面给写加上for

直给写操作加for

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
wg := sync.WaitGroup{}
var ch = make(chan string)
 
read := func() {
    fmt.Println("reading")
    s := <-ch
    fmt.Println("read:", s)
    wg.Done()
}
 
write := func() {
    for {
        fmt.Println("writing")
        s := "t"
        ch <- s
        fmt.Println("write:", s)
    }
    wg.Done()
}
 
wg.Add(2)
go read()
go write()
 
fmt.Println("waiting")
wg.Wait()
fmt.Println("finish")

输出

waiting
reading
writing
write: t
writing
read: t
fatal error: all goroutines are asleep - deadlock!

报错说所有的协程都睡着,意思就是runtime发现没有能拿来调度的协程了,报错退出。如果是在大项目中,这里则会阻塞,runtime会调度其他可运行的协程。下面把for移到读操作上。

直给读操作加for

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
wg := sync.WaitGroup{}
var ch = make(chan string)
 
read := func() {
    for {
        fmt.Println("reading")
        s := <-ch
        fmt.Println("read:", s)
    }
    wg.Done()
}
 
write := func() {
    fmt.Println("writing")
    s := "t"
    ch <- s
    fmt.Println("write:", s)
    wg.Done()
}
 
wg.Add(2)
go read()
go write()
 
fmt.Println("waiting")
wg.Wait()
fmt.Println("finish")

输出

waiting
reading
writing
write: t
read: t
reading
fatal error: all goroutines are asleep - deadlock!

跟上面现象基本一样,不再赘述,然后给俩操作都加上for

读写都加for

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
wg := sync.WaitGroup{}
var ch = make(chan string)
 
read := func() {
    for {
        fmt.Println("reading")
        s := <-ch
        fmt.Println("read:", s)
    }
    wg.Done()
}
 
write := func() {
    for {
        fmt.Println("writing")
        s := "t"
        ch <- s
        fmt.Println("write:", s)
    }
    wg.Done()
}
 
wg.Add(2)
go read()
go write()
 
fmt.Println("waiting")
wg.Wait()
fmt.Println("finish")

输出

waiting
writing
reading
read: t
write: t
writing
reading
read: t
reading
write: t
writing
write: t
writing
...

结果当然就是死循环了,这个很好理解。接下来才是本文的重点:读数据的第二个参数。我们先保持其他的都不动,在读的时候接收第二个返回值。

读channel的第二个返回值

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
wg := sync.WaitGroup{}
var ch = make(chan string)
 
read := func() {
    for {
        fmt.Println("reading")
        s, ok := <-ch
        fmt.Println("read:", s, ok)
    }
    wg.Done()
}
 
write := func() {
    for {
        fmt.Println("writing")
        s := "t"
        ch <- s
        fmt.Println("write:", s)
    }
    wg.Done()
}
 
wg.Add(2)
go read()
go write()
 
fmt.Println("waiting")
wg.Wait()
fmt.Println("finish")

输出

waiting
writing
reading
read: t true
reading
write: t
writing
write: t
writing
read: t true
reading
read: t true
reading
write: t
...

可以看出来,这第二个返回值是个bool类型,目前全都是true。那么什么时候会是false呢,把channel关上试试。为了更直观,把字符串的长度一起输出

关闭channel继续读

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
wg := sync.WaitGroup{}
var ch = make(chan string)
 
read := func() {
    for {
        fmt.Println("reading")
        s, ok := <-ch
        fmt.Println("read:", len(s), s, ok)
    }
    wg.Done()
}
 
write := func() {
    for i := 0; i < 5; i++ {
        fmt.Println("writing")
        s := "t"
        ch <- s
        fmt.Println("write:", s)
    }
    wg.Done()
    close(ch)
}
 
wg.Add(2)
go read()
go write()
 
fmt.Println("waiting")
wg.Wait()
fmt.Println("finish")

输出

waiting
writing
reading
read: 1 t true
reading
write: t
writing
write: t
writing
read: 1 t true
reading
read: 1 t true
reading
write: t
writing
write: t
writing
read: 1 t true
reading
read: 1 t true
reading
write: t
read: 0  false
reading
read: 0  false
reading
read: 0  false
...

接下来就是很规律的死循环了。这样是不是可以猜测,从已经close的channle读数据,会读到该数据类型的零值,且第二个返回值为false?再试试给channel加个buffer,先写完关上再开始读

写完然后关闭channel再开始读

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
wg := sync.WaitGroup{}
var ch = make(chan string, 5)
 
read := func() {
    for {
        fmt.Println("reading")
        s, ok := <-ch
        fmt.Println("read:", len(s), s, ok)
    }
    wg.Done()
}
 
write := func() {
    for i := 0; i < 5; i++ {
        fmt.Println("writing")
        s := "t"
        ch <- s
        fmt.Println("write:", s)
    }
    wg.Done()
    close(ch)
    fmt.Println("closed")
}
 
wg.Add(2)
write()
go read()
 
fmt.Println("waiting")
wg.Wait()
fmt.Println("finish")

输出

writing
write: t
writing
write: t
writing
write: t
writing
write: t
writing
write: t
closed
waiting
reading
read: 1 t true
reading
read: 1 t true
reading
read: 1 t true
reading
read: 1 t true
reading
read: 1 t true
reading
read: 0  false
reading
read: 0  false
reading
read: 0  false
...

我们把写操作前的go关键字去了,并且在关闭channel之后加了log。可以很清晰的看到,先往channel里写了5次,然后close了,之后才有wait及read的log。并且前5个ok是true,后面循环输出false。现在我们可以得出结论当channel关闭且数据都读完了,再读数据会读到该数据类型的零值,且第二个返回值为false。下面再套上select

加个select

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
wg := sync.WaitGroup{}
var ch = make(chan string, 5)
 
read := func() {
    for {
        fmt.Println("reading")
        select {
        case s, ok := <-ch:
            fmt.Println("read:", len(s), s, ok)
        }
 
    }
    wg.Done()
}
 
write := func() {
    for i := 0; i < 5; i++ {
        fmt.Println("writing")
        s := "t"
        ch <- s
        fmt.Println("write:", s)
    }
    wg.Done()
    close(ch)
    fmt.Println("closed")
}
 
wg.Add(2)
write()
go read()
 
fmt.Println("waiting")
wg.Wait()
fmt.Println("finish")

输出

writing
write: t
writing
write: t
writing
write: t
writing
write: t
writing
write: t
closed
waiting
reading
read: 1 t true
reading
read: 1 t true
reading
read: 1 t true
reading
read: 1 t true
reading
read: 1 t true
reading
read: 0  false
reading
read: 0  false
reading
read: 0  false
...

很明显跟上面现象一致,如果忘了关闭channel呢?

channel未及时关闭

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
wg := sync.WaitGroup{}
var ch = make(chan string, 5)
 
read := func() {
    for {
        fmt.Println("reading")
        select {
        case s, ok := <-ch:
            fmt.Println("read:", len(s), s, ok)
        }
 
    }
    wg.Done()
}
 
write := func() {
    for i := 0; i < 5; i++ {
        fmt.Println("writing")
        s := "t"
        ch <- s
        fmt.Println("write:", s)
    }
    wg.Done()
    //close(ch)
    //fmt.Println("closed")
}
 
wg.Add(2)
write()
go read()
 
fmt.Println("waiting")
wg.Wait()
fmt.Println("finish")

输出

writing
write: t
writing
write: t
writing
write: t
writing
write: t
writing
write: t
waiting
reading
read: 1 t true
reading
read: 1 t true
reading
read: 1 t true
reading
read: 1 t true
reading
read: 1 t true
reading
fatal error: all goroutines are asleep - deadlock!

睡着了,然后报错。跟上面情况一样,如果是在大项目中,runtime会调度其他可运行的协程。最后来总结一下怎么操作才算优(sao)雅(qi)。

总结

  • 对写的一方来说,一定记着及时关闭channel,避免出现协程泄露。虽然它占得资源少,省点电不香么。
  • 对读的一方来说,除非十分确定数据的个数,最好是用for来读数据,省的在“管儿”里有“野数据”造成内存泄露。同时根据第二个返回值的真假来控制for循环,避免出现“无效工作量”

到此这篇关于golang channel读取数据的几种情况的文章就介绍到这了,更多相关golang channel读取内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://blog.csdn.net/xmh19936688/article/details/106280737

延伸 · 阅读

精彩推荐
  • GolangGo语言库系列之flag的具体使用

    Go语言库系列之flag的具体使用

    这篇文章主要介绍了Go语言库系列之flag的具体使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面...

    平也5132020-06-08
  • GolangGo语言上下文context底层原理

    Go语言上下文context底层原理

    这篇文章主要介绍了Go语言上下文context底层原理,context是Go中用来进程通信的一种方式,其底层是借助channl与snyc.Mutex实现的,更多相关内容需要的小伙伴可...

    树獭叔叔4782022-10-14
  • GolangGo实现基于RSA加密算法的接口鉴权

    Go实现基于RSA加密算法的接口鉴权

    这篇文章主要介绍了Go实现基于RSA加密算法的接口鉴权,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...

    K8sCat11822021-08-11
  • GolangGo语言使用对称加密的示例详解

    Go语言使用对称加密的示例详解

    在项目开发中,我们经常会遇到需要使用对称密钥加密的场景,比如客户端调用接口时,参数包含手机号、身份证号或银行卡号等。本文将详细讲解Go语言...

    frank9402022-10-13
  • GolangGolang实现将中文转化为拼音

    Golang实现将中文转化为拼音

    这篇文章主要为大家详细介绍了如何通过Golang实现将中文转化为拼音功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下...

    爷来辣4492023-02-06
  • GolangGo pprof内存指标含义备忘录及案例分析

    Go pprof内存指标含义备忘录及案例分析

    这篇文章主要介绍了Go pprof内存指标含义备忘录问题,小编特此把问题及案例分享到脚本之家平台供大家学习,需要的朋友可以参考下 ...

    yoko blog6462020-06-07
  • Golang一文带你掌握Go语言中的文件读取操作

    一文带你掌握Go语言中的文件读取操作

    这篇文章主要和大家分享一下Go语言中的文件读取操作,文中的示例代码讲解详细,对我们学习Go语言有一定的帮助,需要的小伙伴可以参考一下...

    陈明勇9882022-12-07
  • Golanggo module化 import 调用本地模块 tidy的方法

    go module化 import 调用本地模块 tidy的方法

    这篇文章主要介绍了go module化 import 调用本地模块 tidy的相关知识,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友...

    Hoto Cocoa11042022-11-17