千家信息网

golang中channel怎么使用

发表于:2025-01-19 作者:千家信息网编辑
千家信息网最后更新 2025年01月19日,今天小编给大家分享一下golang中channel怎么使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我
千家信息网最后更新 2025年01月19日golang中channel怎么使用

今天小编给大家分享一下golang中channel怎么使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。

    什么是 channel 管道

    它是一个数据管道,可以往里面写数据,从里面读数据。

    channel 是 goroutine 之间数据通信桥梁,而且是线程安全的。

    channel 遵循先进先出原则。

    写入,读出数据都会加锁。

    channel 可以分为 3 种类型:

    • 只读 channel,单向 channel

    • 只写 channel,单向 channel

    • 可读可写 channel

    channel 还可按是否带有缓冲区分为:

    带缓冲区的 channel,定义了缓冲区大小,可以存储多个数据

    不带缓冲区的 channel,只能存一个数据,并且只有当该数据被取出才能存下一个数据

    channel 的基本使用

    定义和声明

    // 只读 channelvar readOnlyChan <-chan int  // channel 的类型为 int// 只写 channelvar writeOnlyChan chan<- int// 可读可写var ch chan int// 或者使用 make 直接初始化readOnlyChan1 := make(<-chan int, 2)  // 只读且带缓存区的 channelreadOnlyChan2 := make(<-chan int)   // 只读且不带缓存区 channelwriteOnlyChan3 := make(chan<- int, 4) // 只写且带缓存区 channelwriteOnlyChan4 := make(chan<- int) // 只写且不带缓存区 channelch := make(chan int, 10)  // 可读可写且带缓存区ch <- 20  // 写数据i := <-ch  // 读数据i, ok := <-ch  // 还可以判断读取的数据

    chan_var.go

    package mainimport (    "fmt")func main() {    // var 声明一个 channel,它的零值是nil    var ch chan int    fmt.Printf("var: the type of ch is %T \n", ch)    fmt.Printf("var: theval of ch is %v \n", ch)    if ch == nil {        // 也可以用make声明一个channel,它返回的值是一个内存地址        ch = make(chan int)        fmt.Printf("make: the type of ch is %T \n", ch)        fmt.Printf("make: theval of ch is %v \n", ch)    }    ch3 := make(chan string, 10)    fmt.Printf("make: the type of ch3 is %T \n", ch3)    fmt.Printf("make: theval of ch3 is %v \n", ch3)}// 输出:// var: the type of ch is chan int// var: theval of ch is // make: the type of ch is chan int// make: theval of ch is 0xc000048060// make: the type of ch3 is chan string// make: theval of ch3 is 0xc000044060

    操作channel的3种方式

    操作 channel 一般有如下三种方式:

    • 读 <-ch

    • 写 ch<-

    • 关闭 close(ch)

    操作nil的channel正常channel已关闭的channel
    读 <-ch阻塞成功或阻塞读到零值
    写 ch<-阻塞成功或阻塞panic
    关闭 close(ch)panic成功panic

    注意 对于 nil channel 的情况,有1个特殊场景:

    当 nil channel 在 select 的某个 case 中时,这个 case 会阻塞,但不会造成死锁。

    单向 channel

    单向 channel:只读和只写的 channel

    chan_uni.go

    package mainimport "fmt"func main() {        // 单向 channel,只写channel        ch := make(chan<- int)        go testData(ch)        fmt.Println(<-ch)}func testData(ch chan<- int) {        ch <- 10// 运行输出// ./chan_uni.go:9:14: invalid operation: <-ch (receive from send-only type chan<- int)// 报错,它是一个只写 send-only channel

    把上面代码main()函数里初始化的单向channel,修改为可读可写channel,再运行

    chan_uni2.go

    package mainimport "fmt"func main() {    // 把上面代码main()函数初始化的单向 channel 修改为可读可写的 channel        ch := make(chan int)        go testData(ch)        fmt.Println(<-ch)}func testData(ch chan<- int) {        ch <- 10}// 运行输出:// 10// 没有报错,可以正常输出结果

    带缓冲和不带缓冲的 channel

    不带缓冲区 channel

    chan_unbuffer.go

    package mainimport "fmt"func main() {    ch := make(chan int) // 无缓冲的channel    go unbufferChan(ch)    for i := 0; i < 10; i++ {        fmt.Println("receive ", <-ch) // 读出值    }}func unbufferChan(ch chan int) {        fmt.Println("send ", i)        ch <- i // 写入值// 输出send  0send  1receive  0receive  1send  2send  3receive  2receive  3send  4send  5receive  4receive  5send  6send  7receive  6receive  7send  8send  9receive  8receive  9
    带缓冲区 channel

    chan_buffer.go

    package mainimport (        "fmt")func main() {        ch := make(chan string, 3)        ch <- "tom"        ch <- "jimmy"        ch <- "cate"        fmt.Println(<-ch)        fmt.Println(<-ch)        fmt.Println(<-ch)}// 运行输出:// tom// jimmy// cate

    再看一个例子:chan_buffer2.go

    package mainimport (        "fmt"        "time")var c = make(chan int, 5)func main() {        go worker(1)        for i := 1; i < 10; i++ {                c <- i                fmt.Println(i)        }}func worker(id int) {        for {                _ = <-c// 运行输出:// 1// 2// 3// 4// 5// 6// 7// 8// 9

    判断 channel 是否关闭

    if v, ok := <-ch; ok {    fmt.Println(ch)}

    说明:

    • ok 为 true,读到数据,且管道没有关闭

    • ok 为 false,管道已关闭,没有数据可读

    读已经关闭的 channel 会读到零值,如果不确定 channel 是否关闭,可以用这种方法来检测。

    range and close

    range 可以遍历数组,map,字符串,channel等。

    一个发送者可以关闭 channel,表明没有任何数据发送给这个 channel 了。接收者也可以测试channel是否关闭,通过 v, ok := <-ch 表达式中的 ok 值来判断 channel 是否关闭。上一节已经说明 ok 为 false 时,表示 channel 没有接收任何数据,它已经关闭了。

    注意:仅仅只能是发送者关闭一个 channel,而不能是接收者。给已经关闭的 channel 发送数据会导致 panic。

    Note: channels 不是文件,你通常不需要关闭他们。那什么时候需要关闭?当要告诉接收者没有值发送给 channel 了,这时就需要了。

    比如终止 range 循环。

    当 for range 遍历 channel 时,如果发送者没有关闭 channel 或在 range 之后关闭,都会导致 deadlock(死锁)。

    下面是一个会产生死锁的例子:

    package mainimport "fmt"func main() {        ch := make(chan int)        go func() {                for i := 0; i < 10; i++ {                        ch <- i                }        }()        for val := range ch {                fmt.Println(val)        }        close(ch) // 这里关闭channel已经"通知"不到range了,会触发死锁。              // 不管这里是否关闭channel,都会报死锁,close(ch)的位置就不对。              // 且关闭channel的操作者也错了,只能是发送者关闭channel}// 运行程序输出// 0// 1// 2// 3// 4// 5// 6// 7// 8// 9// fatal error: all goroutines are asleep - deadlock!

    改正也很简单,把 close(ch) 移到 go func(){}() 里,代码如下

    go func() {    for i := 0; i < 10; i++ {        ch <- i    }    close(ch)}()

    这样程序就可以正常运行,不会报 deadlock 的错误了。

    把上面程序换一种方式来写,chan_range.go

    package mainimport (        "fmt")func main() {        ch := make(chan int)        go test(ch)        for val := range ch { //                fmt.Println("get val: ", val)        }}func test(ch chan int) {        for i := 0; i < 5; i++ {                ch <- i        }        close(ch)}// 运行输出:// get val:  0// get val:  1// get val:  2// get val:  3// get val:  4

    发送者关闭 channel 时,for range 循环自动退出。

    for 读取channel

    用 for 来不停循环读取 channel 里的数据。

    把上面的 range 程序修改下,chan_for.go

    package mainimport (        "fmt")func main() {        ch := make(chan int)        go test(ch)        for {                val, ok := <-ch                if ok == false {// ok 为 false,没有数据可读                        break // 跳出循环                }                fmt.Println("get val: ", val)        }}func test(ch chan int) {        for i := 0; i < 5; i++ {                ch <- i        }        close(ch)}// 运行输出:// get val:  0// get val:  1// get val:  2// get val:  3// get val:  4

    select 使用

    例子 chan_select.go

    package mainimport "fmt"// https://go.dev/tour/concurrency/5func fibonacci(ch, quit chan int) {        x, y := 0, 1        for {                select {                case ch <- x:                        x, y = y, x+y                case <-quit:                        fmt.Println("quit")                        return                }        }}func main() {        ch := make(chan int)        quit := make(chan int)        go func() {                for i := 0; i < 10; i++ {                        fmt.Println(<-ch)                }                quit <- 0        }()        fibonacci(ch, quit)}// 运行输出:// 0// 1// 1// 2// 3// 5// 8// 13// 21// 34// quit

    channel 的一些使用场景

    1. 作为goroutine的数据传输管道

    package mainimport "fmt"// https://go.dev/tour/concurrency/2func sums(s []int, c chan int) {        sum := 0        for _, v := range s {                sum += v        }        c <- sum}func main() {        s := []int{7, 2, 8, -9, 4, 0}        c := make(chan int)        go sums(s[:len(s)/2], c)        go sums(s[len(s)/2:], c)        x, y := <-c, <-c // receive from c        fmt.Println(x, y, x+y)

    用 goroutine 和 channel 分批求和

    2. 同步的channel

    没有缓冲区的 channel 可以作为同步数据的管道,起到同步数据的作用。

    对没有缓冲区的 channel 操作时,发送的 goroutine 和接收的 goroutine 需要同时准备好,也就是发送和接收需要一一配对,才能完成发送和接收的操作。

    如果两方的 goroutine 没有同时准备好,channel 会导致先执行发送或接收的 goroutine 阻塞等待。这就是没有缓冲区的 channel 作为数据同步的作用。

    gobyexample 中的一个例子:

    package mainimport (        "fmt"        "time")//https://gobyexample.com/channel-synchronizationfunc worker(done chan bool) {        fmt.Println("working...")        time.Sleep(time.Second)        fmt.Println("done")        done <- true}func main() {        done := make(chan bool, 1)        go worker(done)        <-done}

    注意:同步的 channel 千万不要在同一个 goroutine 协程里发送和接收数据。可能导致deadlock死锁。

    3. 异步的channel

    有缓冲区的 channel 可以作为异步的 channel 使用。

    有缓冲区的 channel 也有操作注意事项:

    如果 channel 中没有值了,channel 为空了,那么接收者会被阻塞。

    如果 channel 中的缓冲区满了,那么发送者会被阻塞。

    注意:有缓冲区的 channel,用完了要 close,不然处理这个channel 的 goroutine 会被阻塞,形成死锁。

    package mainimport (        "fmt")func main() {        ch := make(chan int, 4)        quitChan := make(chan bool)        go func() {                for v := range ch {                        fmt.Println(v)                }                quitChan <- true // 通知用的channel,表示这里的程序已经执行完了        }()        ch <- 1        ch <- 2        ch <- 3        ch <- 4        ch <- 5        close(ch)  // 用完关闭channel        <-quitChan // 接到channel通知后解除阻塞,这也是channel的一种用法}

    4.channel 超时处理

    channel 结合 time 实现超时处理。

    当一个 channel 读取数据超过一定时间还没有数据到来时,可以得到超时通知,防止一直阻塞当前 goroutine。

    chan_timeout.go

    package mainimport (        "fmt"        "time")func main() {        ch := make(chan int)        quitChan := make(chan bool)        go func() {                for {                        select {                        case v := <-ch:                                fmt.Println(v)                        case <-time.After(time.Second * time.Duration(3)):                                quitChan <- true                                fmt.Println("timeout, send notice")                                return                        }                }        }()        for i := 0; i < 4; i++ {                ch <- i        }        <-quitChan // 输出值,相当于收到通知,解除主程阻塞        fmt.Println("main quit out")}

    使用 channel 的注意事项及死锁分析

    未初始化的 channel 读写关闭操作

    1.读:未初始化的channel,读取里面的数据时,会造成死锁deadlock

    var ch chan int<-ch  // 未初始化channel读数据会死锁

    2.写:未初始化的channel,往里面写数据时,会造成死锁deadlock

    var ch chan intch<-  // 未初始化channel写数据会死锁

    3.关闭:未初始化的channel,关闭该channel时,会panic

    var ch chan intclose(ch) // 关闭未初始化channel,触发panic

    已初始化的 channel 读写关闭操作

    1. 已初始化,没有缓冲区的channel
     // 代码片段1   func main() {        ch := make(chan int)        ch <- 4   }

    代码片段1:没有缓冲channel,且只有写入没有读取,会产生死锁

    // 代码片段2   func main() {       ch := make(chan int)       val, ok := <-ch   }

    代码片段2:没有缓冲channel,且只有读取没有写入,会产生死锁

    // 代码片段3   func main() {       ch := make(chan int)       val, ok := <-ch       if ok {           fmt.Println(val)       }       ch <- 10 // 这里进行写入。但是前面已经产生死锁了   }

    代码片段3:没有缓冲channel,既有写入也有读出,但是在代码 val, ok := <-c 处已经产生死锁了。下面代码执行不到。

    // 代码片段4   func main() {         ch := make(chan int)         ch <- 10         go readChan(ch)                time.Sleep(time.Second * 2)   }      func readChan(ch chan int) {         for {                 val, ok := <-ch                 fmt.Println("read ch: ", val)                 if !ok {                         break                 }         }   }

    代码片段4:没有缓冲channel,既有写入也有读出,但是运行程序后,报错 fatal error: all goroutines are asleep - deadlock!

    这是因为往 channle 里写入数据的代码 ch <- 10,这里写入数据时就已经产生死锁了。把 ch<-10go readChan(ch) 调换位置,程序就能正常运行,不会产生死锁。

     // 代码片段5   func main() {         ch := make(chan int)            go writeChan(ch)         for {                 val, ok := <-ch                 fmt.Println("read ch: ", val)                 if !ok {                         break                 }         }         time.Sleep(time.Second)       fmt.Println("end")   }   func writeChan(ch chan int) {         for i := 0; i < 4; i++ {                 ch <- i

    代码片段5:没有缓冲的channel,既有写入,也有读出,与上面几个代码片段不同的是,写入channel的数据不是一个。

    思考一下,这个程序会产生死锁吗?10 秒时间思考下,先不要看下面。

    也会产生死锁,它会输出完数据后,报错 fatal error: all goroutines are asleep - deadlock!

    为什么呢?这个程序片段,既有读也有写而且先开一个goroutine写数据,为什么会死锁?

    原因在于 main() 里的 for 循环。可能你会问,不是有 break 跳出 for 循环吗?代码是写了,但是程序并没有执行到这里。

    因为 for 会不停的循环,而 val, ok := <-ch, 这里 ok 值一直是 true,因为程序里并没有哪里关闭 channel 啊。你们可以打印这个 ok 值看一看是不是一直是 true。当 for 循环把 channel 里的值读取完了后,程序再次运行到 val, ok := <-ch 时,产生死锁,因为 channel 里没有数据了。

    找到原因了,那解决办法也很简单,在 writeChan 函数里关闭 channel,加上代码 close(ch)。告诉 for 我写完了,关闭 channel 了。

    加上关闭 channel 代码后运行程序:

    read ch:  0 , ok:  trueread ch:  1 , ok:  trueread ch:  2 , ok:  trueread ch:  3 , ok:  trueread ch:  0 , ok:  falseend

    程序正常输出结果。

    对于没有缓冲区的 channel (unbuffered channel) 容易产生死锁的几个代码片段分析,总结下:

    • channel 要用 make 进行初始化操作

    • 读取和写入要配对出现,并且不能在同一个 goroutine 里

    • 一定先用 go 起一个协程执行读取或写入操作

    • 多次写入数据,for 读取数据时,写入者注意关闭 channel(代码片段5)

    2. 已初始化,有缓冲区的 channel
    // 代码片段1func main() {    ch := make(chan int, 1)    val, ok := <-ch}

    代码片段1:有缓冲channel,先读数据,这里会一直阻塞,产生死锁。

    // 代码片段2   func main() {       ch := make(chan int, 1)       ch <- 10   }

    代码片段2:同代码片段1,有缓冲channel,只有写没有读,也会阻塞,产生死锁。

     // 代码片段3   func main() {         ch := make(chan int, 1)         ch <- 10            val, ok := <-ch         if ok {                 fmt.Println(val, ok)         }   }

    代码片段3:有缓冲的channel,有读有写,正常的输出结果。

    有缓冲区的channel总结:

    • 如果 channel 满了,发送者会阻塞

    • 如果 channle 空了,接收者会阻塞

    • 如果在同一个 goroutine 里,写数据操作一定在读数据操作前

    以上就是"golang中channel怎么使用"这篇文章的所有内容,感谢各位的阅读!相信大家阅读完这篇文章都有很大的收获,小编每天都会为大家更新不同的知识,如果还想学习更多的知识,请关注行业资讯频道。

    数据 代码 缓冲 死锁 片段 缓冲区 阻塞 输出 程序 运行 循环 单向 发送者 管道 接收者 存区 同步 例子 只有 完了 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 深圳市椰子互娱网络技术有限公司网站 四川大学网络安全学院吧 永恒之塔怀旧服服务器状态查看 有关青少年网络安全的事例 河北什么是软件开发推广 金山云网络技术有限公司审计 网络安全服务公司服务内容 sw安装协同服务器错误 怎么开发api同步数据库 有关于互联网科技的知识 我的世界天龙八部服务器 云服务器上油管 网络安全工程师的职等职级 美国网络安全工程师收入 上海软件开发园有哪些 易观最新app数据库 在大理学习网络技术 网络安全哪年开始的 查看数据库的连接信息 公众号服务器租赁费 北京陌香互联网科技有限公司 红蜻蜓ip数据库 手机上网显示服务器没有响应 青岛旭阳网络技术有限公司赵安 神州数码软件开发怎么样 美国做数据库的企业 地城之光与服务器 安防监控平台管理服务器 数据库课题设计选题新颖 计算机网络技术能升什么专业
    0