深入理解 Golang Channel 并发编程
2021/8/3 14:06:39
本文主要是介绍深入理解 Golang Channel 并发编程,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
一、基础简介
Golang使用Groutine和channels实现了CSP(Communicating Sequential Processes)模型,channles在goroutine的通信和同步中承担着重要的角色。
这种方式的优点是通过提供原子的通信原语,避免了竞态情形(race condition)下复杂的锁机制。
channel 可以看成一个 FIFO 队列,对 FIFO 队列的读写都是原子的操作,不需要加锁。
Golang 语言中Channel类型的定义格式如下:
chan Tr // 可以接收和发送类型为 Tr 的数据 chan <- float64 // 只可以用来发送 float64 类型的数据 <- chan int // 只可以用来接收 int 类型的数据
可选的<-代表channel的方向。如果没有指定方向,那么Channel就是双向的,既可以接收数据,也可以发送数据。
默认情况下,channel发送方和接收方会一直阻塞直到对方准备好发送或者接收,这就使得Go语言无需加锁或者其他条件,天然支持了并发。
例:
c := make(chan bool) //创建一个无缓冲的bool型Channel c <- x //向一个Channel发送一个值 <- c //从一个Channel中接收一个值 x = <- c //从Channel c接收一个值并将其存储到x中 x, ok = <- c //从Channel接收一个值,如果channel关闭了或没有数据,那么ok将被置为false
使用 make 初始化Channel,还可以设置容量:
make(chan int, 100) //创建一个有缓冲的int型Channel
Channel缓冲介绍
容量(capacity)代表Channel容纳的最多的元素的数量,代表Channel的缓冲的大小。 如果没有设置容量,或者容量设置为0, 说明Channel没有缓冲。
在实际开发中,你可以在多个goroutine从/往一个channel 中 receive/send 数据, 不必考虑额外的同步措施。
Channel可以作为一个先入先出(FIFO)的队列,接收的数据和发送的数据的顺序是一致的,不带缓冲的Channel兼具通信和同步两种特性。
二、channel 应用实例
实例1代码如下
package main import "fmt" func main() { ch1 := make(chan int,2) //send go func() { for i := 0; i < 8; i++ { fmt.Printf("Sender :sending element %v ..\n",i) ch1 <- i } fmt.Println("Sender : close the channel ...") close(ch1) }() //receiver for { elem, ok := <- ch1 if !ok { fmt.Println("Receiver: closed channel..") break } fmt.Printf("Receiver: received an element: %v \n",elem) } fmt.Println("main end..") }
上面示例的程序中,我们创建了一个带缓冲的INT类型Channel,然后在一个goroutine 循环向 channel 中发送序列数据,然后在一个goroutine用 for循环方式读取 <-ch1 的内容。
在这里我们利用Channel 实现了线程间(准确的说,goroutine之间的)通信,因为channel发送方和接收方会一直阻塞直到对方准备好发送或者接收,这就使得我们在程序等待 ch1关闭而无需其他的同步操作。
- 缓存ch1 := make(chan int,0) 或 ch1 := make(chan int); 运行结果如下:
~/gomod/src/channel$ go run channel.go Sender :sending element 0 .. Sender :sending element 1 .. Receiver: received an element: 0 Receiver: received an element: 1 Sender :sending element 2 .. Sender :sending element 3 .. Receiver: received an element: 2 Receiver: received an element: 3 Sender :sending element 4 .. Sender :sending element 5 .. Receiver: received an element: 4 Receiver: received an element: 5 Sender :sending element 6 .. Sender :sending element 7 .. Receiver: received an element: 6 Receiver: received an element: 7 Sender : close the channel ... Receiver: closed channel.. main end..
- 缓存ch1 := make(chan int,5); 运行结果如下:
robot@ubuntu:~/gomod/src/channel$ go run channel.go Sender :sending element 0 .. Sender :sending element 1 .. Sender :sending element 2 .. Sender :sending element 3 .. Sender :sending element 4 .. Sender :sending element 5 .. Sender :sending element 6 .. Receiver: received an element: 0 Receiver: received an element: 1 Receiver: received an element: 2 Receiver: received an element: 3 Receiver: received an element: 4 Receiver: received an element: 5 Receiver: received an element: 6 Sender :sending element 7 .. Sender : close the channel ... Receiver: received an element: 7 Receiver: closed channel.. main end..
- 缓存ch1 := make(chan int,10); 运行结果如下:
robot@ubuntu:~/gomod/src/channel$ go run channel.go Sender :sending element 0 .. Sender :sending element 1 .. Sender :sending element 2 .. Sender :sending element 3 .. Sender :sending element 4 .. Sender :sending element 5 .. Sender :sending element 6 .. Sender :sending element 7 .. Sender : close the channel ... Receiver: received an element: 0 Receiver: received an element: 1 Receiver: received an element: 2 Receiver: received an element: 3 Receiver: received an element: 4 Receiver: received an element: 5 Receiver: received an element: 6 Receiver: received an element: 7 Receiver: closed channel.. main end..
实例2 代码如下:
package main import ( "fmt" "time" ) func main() { N := 10 exit := make (chan struct{}) done := make(chan struct{},N) for i :=0; i< N; i++ { go func(n int){ for { select { case <- exit: fmt.Printf("worker goroutine # %d exit \n\r ",n) done <- struct{}{} return case <- time.After(time.Second): fmt.Printf("worker goroutine # %d is working ..\n", n) } } }(i) } time.Sleep(3*time.Second) fmt.Printf("sender exit channel... \n\r") close(exit) //关闭 channel 通知协程退出线程 for i := 0; i < N;i++ { <- done } fmt.Printf("main goroutine exit..\n") }
运行效果如下:
robot@ubuntu:~/gomod/src/channel$ go run channel1.go worker goroutine # 8 is working .. worker goroutine # 5 is working .. worker goroutine # 0 is working .. worker goroutine # 2 is working .. worker goroutine # 9 is working .. worker goroutine # 4 is working .. worker goroutine # 6 is working .. worker goroutine # 7 is working .. worker goroutine # 3 is working .. worker goroutine # 1 is working .. worker goroutine # 8 is working .. worker goroutine # 9 is working .. worker goroutine # 5 is working .. worker goroutine # 0 is working .. worker goroutine # 2 is working .. worker goroutine # 6 is working .. worker goroutine # 4 is working .. worker goroutine # 7 is working .. worker goroutine # 3 is working .. worker goroutine # 1 is working .. sender exit channel... worker goroutine # 8 exit worker goroutine # 2 exit worker goroutine # 1 exit worker goroutine # 0 exit worker goroutine # 5 exit worker goroutine # 9 exit worker goroutine # 6 exit worker goroutine # 7 exit worker goroutine # 3 exit worker goroutine # 4 exit main goroutine exit..
关闭管道 exit 后,各 goroutine 携程检测到exit管道被关闭,自动退出协程。
这篇关于深入理解 Golang Channel 并发编程的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-20MongoDB教程:从入门到实践详解
- 2024-11-17执行 Google Ads API 查询后返回的是空数组什么原因?-icode9专业技术文章分享
- 2024-11-17google广告数据不同经理账户下的凭证可以获取对方的api数据吗?-icode9专业技术文章分享
- 2024-11-15SendGrid 的 Go 客户端库怎么实现同时向多个邮箱发送邮件?-icode9专业技术文章分享
- 2024-11-15SendGrid 的 Go 客户端库怎么设置header 和 标签tag 呢?-icode9专业技术文章分享
- 2024-11-12Cargo deny安装指路
- 2024-11-02MongoDB项目实战:从入门到初级应用
- 2024-11-01随时随地一键转录,Google Cloud 新模型 Chirp 2 让语音识别更上一层楼
- 2024-10-25Google Cloud动手实验详解:如何在Cloud Run上开发无服务器应用
- 2024-10-24AI ?先驱齐聚 BAAI 2024,发布大规模语言、多模态、具身、生物计算以及 FlagOpen 2.0 等 AI 模型创新成果。