众所周知,Go lang的作用域相对严格,数据之间的通信往往要依靠参数的传递,但如果想在多个协程任务中间做数据通信,就需要通道(channel)的参与,我们可以把数据封装成一个对象,然后把这个对象的指针传入某个通道变量中,另外一个协程从这个通道中读出变量的指针,并处理其指向的内存对象。
通道的声明与创建
package main
import "fmt"
func main() {
var a chan int
if a == nil {
fmt.Println("通道是空的, 不能使用,需要先创建通道")
a = make(chan int)
fmt.Printf("数据类型是: %T", a)
}
}
这里注意,通道声明之后还需要进行创建。
也可以通过海象操作符声明并创建:
package main
import "fmt"
func main() {
a := make(chan int)
fmt.Printf("数据类型是: %T", a)
}
程序返回:
数据类型是: chan int%
如此,一个类型为整形的通道就创建好了。
此外,通道是引用数据类型:
package main
import (
"fmt"
)
func main() {
ch1 := make(chan int)
fmt.Printf("%T,%p\n", ch1, ch1)
test1(ch1)
}
func test1(ch chan int) {
fmt.Printf("%T,%p\n", ch, ch)
}
程序返回:
chan int,0x1400010e060
chan int,0x1400010e060
可以看到,在test1函数内和main函数内通道的地址是一样的,所以他们指向的都是同一个通道。
通道的使用
通道创建之后,即可以在协程之间充当桥梁:
package main
import "fmt"
func job(ch1 chan int) {
ch1 <- 1 } func main() { ch1 :="make(chan" int) fmt.println(ch1) go job(ch1) data 从ch1通道中读取数据 fmt.println("data-->", data)
fmt.Println("main。。over。。。。")
}
</->
这里我们声明一个函数job,把通道作为参数传递进去,注意这里参数类型除了声明通道本身以外,还得声明通道具体的数据类型。
随后在main函数中,可以理解为主协程,创建通道ch1,执行开启协程任务job,在job函数内,往通道内传递数字1
接着,主协程获取通道内由job协程传递的数据:
0x1400006a060
data--> 1
main。。over。。。。
藉此,就完成了数据的传递。
这里需要注意通道的调用语法:
data := <- a 读取通道 <- data 写入通道 < code></->
同步阻塞
而通道的出现,就间接帮我们实现了”阻塞”主协程的目的。
比如,多个协程任务操作一个变量:
package main
import (
"fmt"
)
func job1(number int, squareop chan int) {
sum := 20
sum += number
squareop <- sum } func job2(number int, cubeop chan int) { :="10" +="number" <- main() number ch1 ch2 go job1(number, ch1) job2(number, ch2) num1, num2 <-ch2 fmt.println("final output", num1+num2) < code></->
这里job1和job2两个协程任务同时异步执行,操作number变量,累加后往通道中写入,程序返回:
Final output 30
理论上,如果是并发执行,返回值应该是20或者10,但由于通道的存在,造成协程任务阻塞,变回了同步执行,所以返回了30。
同时,我们需要注意死锁问题,如果一个协程任务在一个通道上发送数据,那么其他的协程任务应该接收数据,如果这种情况不发生,那么程序将在运行时出现死锁。
换句话说,你发送了,就得有人接收,只发不接,或者只收不发,都会变成死锁。
此外,协程任务可以通过close(ch)方法来关闭通道:
package main
import (
"fmt"
)
func job(ch1 chan int) {
// 发送方:3条数据
for i := 0; i < 3; i++ {
ch1 <- i 将i写入通道中 } close(ch1) 将ch1通道关闭了。 func main() { ch1 :="make(chan" int) go job(ch1) * 子goroutine,写出数据3个 每写一个,阻塞一次,主程序读取一次,解除阻塞 主goroutine:循环读 每次读取一个,堵塞一次,子程序,写出一个,解除阻塞 发送发,关闭通道的--->接收方,接收到的数据是该类型的零值,以及false
*/
//主程序中获取通道的数据
for {
v, ok := <-ch1 其他goroutine,显示的调用close方法关闭通道。 if !ok { fmt.println("已经读取了所有的数据,", ok) break } fmt.println("取出数据:", v, fmt.println("main...over....") < code></-ch1></->
这里将0到2写入chl通道,然后关闭通道。主函数里有一个死循环。类似while,它轮询通道是否在发送数据后,使用变量ok进行判断。如果ok是假的,则意味着通道关闭,因此循环结束,否则将会继续进行无限轮询。
select关键字
select 是 Go lang里面的一个流程控制结构,和switch关键字差不多,但是select会随机执行一个可运行的通道通信,如果没有通道通信可运行,它将阻塞,直到有通道通信可运行:
package main
import (
"fmt"
"time"
)
func job(ch1 chan int) {
time.Sleep(2 * time.Second)
ch1 <- 200 } func main() { ch1 :="make(chan" int) ch2 go job(ch1) job(ch2) select case num1 fmt.println("ch1中取数据。。", num1) num2, ok if fmt.println("ch2中取数据。。", num2) else fmt.println("ch2通道已经关闭。。") < code></->
这里select会随机选择一个可运行的通道通信逻辑,可能是ch1通道,也有可能是ch2通道:
➜ mydemo git:(master) ✗ go run "/Users/liuyue/wodfan/work/mydemo/hello.go"
ch1中取数据。。 200
➜ mydemo git:(master) ✗ go run "/Users/liuyue/wodfan/work/mydemo/hello.go"
ch1中取数据。。 200
➜ mydemo git:(master) ✗ go run "/Users/liuyue/wodfan/work/mydemo/hello.go"
ch2中取数据。。 200
➜ mydemo git:(master) ✗
综上,Golang的通道其实就是将协程任务进行隔离,编写并发逻辑时,关注通道即可,说白了,Golang的通道就是Python多进程通信中的管道,Golang虽然没有显性的多进程调用,但其协程调度底层就是多进程之间的通信,因为只有多进程才可能利用CPU的多核资源。
Original: https://www.cnblogs.com/v3ucn/p/16640477.html
Author: 刘悦的技术博客
Title: 大道如青天,协程来通信,Go lang1.18入门精炼教程,由白丁入鸿儒,Go lang通道channel的使用EP14
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/516203/
转载文章受原作者版权保护。转载请注明原作者出处!