栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Go语言

第010节:channel通道

Go语言 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

通道可以被认为是Goroutines通信的管道。类似于管道中的水从一端到另一端的流动,数据可以从一端发送到另一端,通过通道接收。

在前面讲Go语言的并发时候,我们就说过,当多个Goroutine想实现共享数据的时候,虽然也提供了传统的同步机制,但是Go语言强烈建议的是使用Channel通道来实现Goroutines之间的通信。

“不要通过共享内存来通信,而应该通过通信来共享内存” 这是一句风靡golang社区的经典语

Go语言中,要传递某个数据给另一个goroutine(协程),可以把这个数据封装成一个对象,然后把这个对象的指针传入某个channel中,另外一个goroutine从这个channel中读出这个指针,并处理其指向的内存对象。Go从语言层面保证同一个时间只有一个goroutine能够访问channel里面的数据,为开发者提供了一种优雅简单的工具,所以Go的做法就是使用channel来通信,通过通信来传递内存数据,使得内存数据在不同的goroutine中传递,而不是使用共享内存来通信。

一、 什么是通道

1.1 通道的概念

通道是什么,通道就是goroutine之间的通道。它可以让goroutine之间相互通信。

每个通道都有与其相关的类型。该类型是通道允许传输的数据类型。(通道的零值为nil。nil通道没有任何用处,因此通道必须使用类似于map和切片的方法来定义。)

1.2 通道的声明

声明一个通道和定义一个变量的语法一样:

//声明通道var 通道名 chan 数据类型//创建通道:如果通道为nil(就是不存在),就需要先创建通道通道名 = make(chan 数据类型)

示例代码:

package mainimport "fmt"func main() {    var a chan int    if a == nil {        fmt.Println("channel 是 nil 的, 不能使用,需要先创建通道。。")        a = make(chan int)        fmt.Printf("数据类型是: %T", a)    }}

运行结果:

channel 是 nil 的, 不能使用,需要先创建通道。。数据类型是: chan int

也可以简短的声明:

a := make(chan int) 

1.3 channel的数据类型

channel是引用类型的数据,在作为参数传递的时候,传递的是内存地址。

示例代码:

package mainimport (    "fmt")func main() {    ch1 := make(chan int)    fmt.Printf("%T,%pn",ch1,ch1)    test1(ch1)}func test1(ch chan int){    fmt.Printf("%T,%pn",ch,ch)}

运行结果:


第010节:channel通道

我们能够看到,ch和ch1的地址是一样的,说明它们是同一个通道。

1.4 通道的注意点

Channel通道在使用的时候,有以下几个注意点:

1.用于goroutine,传递消息的。

2.通道,每个都有相关联的数据类型,
nil chan,不能使用,类似于nil map,不能直接存储键值对

3.使用通道传递数据:<-
chan <- data,发送数据到通道。向通道中写数据
data <- chan,从通道中获取数据。从通道中读数据

4.阻塞:
发送数据:chan <- data,阻塞的,直到另一条goroutine,读取数据来解除阻塞
读取数据:data <- chan,也是阻塞的。直到另一条goroutine,写出数据解除阻塞。

5.本身channel就是同步的,意味着同一时间,只能有一条goroutine来操作。

最后:通道是goroutine之间的连接,所以通道的发送和接收必须处在不同的goroutine中。

二、通道的使用语法

2.1 发送和接收

发送和接收的语法:

data:= <- a // read from channel a  a <- data // write to channel a

在通道上箭头的方向指定数据是发送还是接收。

另外:

v, ok := <- a //从一个channel中读取

2.2 发送和接收默认是阻塞的

一个通道发送和接收数据,默认是阻塞的。当一个数据被发送到通道时,在发送语句中被阻塞,直到另一个Goroutine从该通道读取数据。相对地,当从通道读取数据时,读取被阻塞,直到一个Goroutine将数据写入该通道。

这些通道的特性是帮助Goroutines有效地进行通信,而无需像使用其他编程语言中非常常见的显式锁或条件变量。

示例代码:

package mainimport "fmt"func main() {    var ch1 chan bool       //声明,没有创建    fmt.Println(ch1)        //    fmt.Printf("%Tn", ch1) //chan bool    ch1 = make(chan bool)   //0xc0000a4000,是引用类型的数据    fmt.Println(ch1)    go func() {        for i := 0; i < 10; i++ {            fmt.Println("子goroutine中,i:", i)        }        // 循环结束后,向通道中写数据,表示要结束了。。        ch1 <- true        fmt.Println("结束。。")    }()    data:= <-ch1 // 从ch1通道中读取数据    fmt.Println("data-->", data)    fmt.Println("main。。over。。。。")}

运行结果:


第010节:channel通道

在上面的程序中,我们先创建了一个chan bool通道。然后启动了一条子Goroutine,并循环打印10个数字。然后我们向通道ch1中写入输入true。然后在主goroutine中,我们从ch1中读取数据。这一行代码是阻塞的,这意味着在子Goroutine将数据写入到该通道之前,主goroutine将不会执行到下一行代码。因此,我们可以通过channel实现子goroutine和主goroutine之间的通信。当子goroutine执行完毕前,主goroutine会因为读取ch1中的数据而阻塞。从而保证了子goroutine会先执行完毕。这就消除了对时间的需求。在之前的程序中,我们要么让主goroutine进入睡眠,以防止主要的Goroutine退出。要么通过WaitGroup来保证子goroutine先执行完毕,主goroutine才结束。

示例代码:以下代码加入了睡眠,可以更好的理解channel的阻塞

package mainimport (    "fmt"    "time")func main() {    ch1 := make(chan int)    done := make(chan bool) // 通道    go func() {        fmt.Println("子goroutine执行。。。")        time.Sleep(3 * time.Second)        data:= <-ch1 // 从通道中读取数据        fmt.Println("data:", data)        done <- true    }()    // 向通道中写数据。。    time.Sleep(5 * time.Second)    ch1 <- 100    <-done    fmt.Println("main。。over")}

运行结果:


第010节:channel通道

再一个例子,这个程序将打印一个数字的个位数的平方和。

package mainimport (      "fmt")func calcSquares(number int, squareop chan int) {      sum := 0    for number != 0 {        digit := number % 10        sum += digit * digit        number /= 10    }    squareop <- sum}func calcCubes(number int, cubeop chan int) {      sum := 0     for number != 0 {        digit := number % 10        sum += digit * digit * digit        number /= 10    }    cubeop <- sum} func main() {      number := 589    sqrch := make(chan int)    cubech := make(chan int)    go calcSquares(number, sqrch)    go calcCubes(number, cubech)    squares, cubes := <-sqrch, <-cubech    fmt.Println("Final output", squares + cubes)}

运行结果:

Final output 1536

2.3 死锁

使用通道时要考虑的一个重要因素是死锁。如果Goroutine在一个通道上发送数据,那么预计其他的Goroutine应该接收数据。如果这种情况不发生,那么程序将在运行时出现死锁。

类似地,如果Goroutine正在等待从通道接收数据,那么另一些Goroutine将会在该通道上写入数据,否则程序将会死锁。

示例代码:

package mainfunc main() {      ch := make(chan int)    ch <- 5}

报错:

fatal error: all goroutines are asleep - deadlock!goroutine 1 [chan send]:main.main()    /Users/ruby/go/src/l_goroutine/demo08_chan.go:5 +0x50

在主流的编程语言中为了保证多线程之间共享数据安全性和一致性,都会提供一套基本的同步工具集,如锁,条件变量,原子操作等等。Go语言标准库也毫不意外的提供了这些同步机制,使用方式也和其他语言也差不多。
除了这些基本的同步手段,Go语言还提供了一种新的同步机制: Channel,它在Go语言中是一个像int, float32等的基本类型,一个channel可以认为是一个能够在多个Goroutine之间传递某一类型的数据的管道。Go中的channel无论是实现机制还是使用场景都和Java中的BlockingQueue很接近。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/793668.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号