文章目录
- 将 channel 用作通信机制
- Channel 语法
- 无缓冲 channel
- 缓冲 channels
- channel 与 goroutine
- 缓冲 channels 示例
- 多路复用
将 channel 用作通信机制
golang学习笔记——将 channel 用作通信机制
golang学习笔记——并发计算斐波纳契数
Go 中的 channel 是 goroutine 之间的通信机制。 请记住 Go 的并发方法是:不是通过共享内存通信;而是通过通信共享内存。当你需要将值从一个 goroutine 发送到另一个时,可以使用通道。 让我们看看它们的工作原理,以及如何开始使用它们来编写并发 Go 程序。
Channel 语法
ch <- x // sends (or writes ) x through channel ch
x = <-ch // x receives (or reads) data sent to the channel ch
<-ch // receives data, but the result is discarded
关闭 channel
close(ch)
无缓冲 channel
使用 make()
函数创建 channel 时,会创建一个无缓冲 channel,这是默认行为。 无缓冲 channel 会阻止发送操作,直到有人准备好接收数据。
package mainimport ("fmt""net/http""time"
)func main() {start := time.Now()apis := []string{"https://mp.csdn.net/","https://dev.azure.com","https://api.somewhereintheinternet.com/","https://gitcode.net/",}ch := make(chan string)for _, api := range apis {go checkAPI(api, ch)}for i := 0; i < len(apis); i++ {fmt.Print(<-ch)}elapsed := time.Since(start)fmt.Printf("Done! It took %v seconds!\n", elapsed.Seconds())
}func checkAPI(api string, ch chan string) {_, err := http.Get(api)if err != nil {ch <- fmt.Sprintf("ERROR: %s is down!\n", api)return}ch <- fmt.Sprintf("SUCCESS: %s is up and running!\n", api)
}
缓冲 channels
下面是一个理解有缓冲 channel 的简单示例
package mainimport ("fmt"
)func send(ch chan string, message string) {ch <- message
}func main() {size := 4ch := make(chan string, size)send(ch, "one")send(ch, "two")send(ch, "three")send(ch, "four")fmt.Println("All data sent to the channel ...")for i := 0; i < size; i++ {fmt.Println(<-ch)}fmt.Println("Done!")
}
输出
All data sent to the channel ...
one
two
three
four
Done!
试着将size改为2
重新运行程序时,将看到以下错误:
fatal error: all goroutines are asleep - deadlock!goroutine 1 [chan send]:
main.send(...)D:/golang2023/main.go:8
main.main()D:/golang2023/main.go:16 +0x97
exit status 2
channel 与 goroutine
channel 与 goroutine 有着紧密的联系。 如果没有另一个 goroutine 从 channel 接收数据,则整个程序可能会永久处于被阻止状态。 正如你所见,这种情况确实会发生。
缓冲 channels 示例
使用之前用于检查 API 的示例,并创建大小为 10 的缓冲通道
package mainimport ("fmt""net/http""time"
)func main() {start := time.Now()apis := []string{"https://management.azure.com","https://dev.azure.com","https://mp.csdn.net/","https://outlook.office.com/","https://api.somewhereintheinternet.com/","https://gitcode.net/",}ch := make(chan string, 10)for _, api := range apis {go checkAPI(api, ch)}for i := 0; i < len(apis); i++ {fmt.Print(<-ch)}elapsed := time.Since(start)fmt.Printf("Done! It took %v seconds!\n", elapsed.Seconds())
}func checkAPI(api string, ch chan string) {_, err := http.Get(api)if err != nil {ch <- fmt.Sprintf("ERROR: %s is down!\n", api)return}ch <- fmt.Sprintf("SUCCESS: %s is up and running!\n", api)
}
多路复用
最后,让我们讨论如何使用 select
关键字与多个通道同时交互。 有时,在使用多个 channel 时,需要等待事件发生。 例如,当程序正在处理的数据中出现异常时,可以包含一些逻辑来取消操作。
select
语句的工作方式类似于 switch
语句,但它适用于 channel。 它会阻止程序的执行,直到它收到要处理的事件。 如果它收到多个事件,则会随机选择一个。
select
语句的一个重要方面是,它在处理事件后完成执行。 如果要等待更多事件发生,则可能需要使用循环。
让我们使用以下程序来看看 select
的运行情况:
package mainimport ("fmt""time"
)func process(ch chan string) {time.Sleep(3 * time.Second)ch <- "Done processing!"
}func replicate(ch chan string) {time.Sleep(1 * time.Second)ch <- "Done replicating!"
}func main() {ch1 := make(chan string)ch2 := make(chan string)go process(ch1)go replicate(ch2)for i := 0; i < 2; i++ {select {case process := <-ch1 :fmt.Println(process)case replicate := <-ch2 :fmt.Println(replicate)}}
}
输出
Done replicating!
Done processing!
请注意,replicate
函数首先完成,这就是首先在终端中看到其输出的原因。 main 函数存在一个循环,因为 select
语句在收到事件后立即结束,但我们仍在等待 process
函数完成。