首页IT科技协程线程进程的区别(大道如青天,协程来通信,Go lang1.18入门精炼教程,由白丁入鸿儒,Go lang通道channel的使用EP14)

协程线程进程的区别(大道如青天,协程来通信,Go lang1.18入门精炼教程,由白丁入鸿儒,Go lang通道channel的使用EP14)

时间2025-07-02 00:08:52分类IT科技浏览4259
导读:众所周知,Go lang的作用域相对严格,数据之间的通信往往要依靠参数的传递,但如果想在多个协程任务中间做数据通信,就需要通道(channel)的参与,我们可以把数据封装成一个对象,然后把这个对象的指针传入某个通道变量中,另外一个协程从这个通道中读出变量的指针,并处理其指向的内存对象。...

众所周知              ,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 // 从ch1通道中读取数据 fmt.Println("data-->", data) fmt.Println("main                    。         。over      。                    。             。   。") }

这里我们声明一个函数job   ,把通道作为参数传递进去              ,注意这里参数类型除了声明通道本身以外                  ,还得声明通道具体的数据类型                   。

随后在main函数中      ,可以理解为主协程           ,创建通道ch1                  ,执行开启协程任务job         ,在job函数内        ,往通道内传递数字1

接着                   ,主协程获取通道内由job协程传递的数据:

0x1400006a060 data--> 1 main                 。。over               。                    。   。            。

藉此            ,就完成了数据的传递                    。

这里需要注意通道的调用语法:

data := <- a // 读取通道 a <- data // 写入通道

同步阻塞

这里需要注意的是    ,通道无论是写入还是读取                    ,都是同步阻塞机制      。即当有协程对通道进行操作的时候               ,其他协程都处于“等待            ”状态,说白了                 ,就是在“排队                    ”                  ,在之前的一篇:并发与并行,同步和异步,Go lang1.18入门精炼教程   ,由白丁入鸿儒              ,Go lang并发编程之GoroutineEP13                  ,我们要么通过sync.WaitGroup来阻塞主协程      ,或者通过time.Sleep(time.Second)方法来阻塞           ,就是怕主协程提前执行完                  ,早成子协程来不及执行         。

而通道的出现         ,就间接帮我们实现了“阻塞      ”主协程的目的                    。

比如        ,多个协程任务操作一个变量:

package main import ( "fmt" ) func job1(number int, squareop chan int) { sum := 20 sum += number squareop <- sum } func job2(number int, cubeop chan int) { sum := 10 sum += number cubeop <- sum } func main() { number := 0 ch1 := make(chan int) ch2 := make(chan int) go job1(number, ch1) go job2(number, ch2) num1, num2 := <-ch1, <-ch2 fmt.Println("Final output", num1+num2) }

这里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, ok) } fmt.Println("main...over....") }

这里将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 := make(chan int) go job(ch1) go job(ch2) select { case num1 := <-ch1: fmt.Println("ch1中取数据                    。   。", num1) case num2, ok := <-ch2: if ok { fmt.Println("ch2中取数据            。                    。", num2) } else { fmt.Println("ch2通道已经关闭      。         。") } } }

这里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的多核资源                   。

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
java将文件夹压缩成zip包(如何通过Java应用程序压缩PDF文档) 探索公司组织架构优化的实践经验和思考方法(探索公司组织架构优化的实践经验和思考)