chan
Go语言通过提供一种轻量级的并发机制——通道(Channel),使得并发编程变得更加容易和直观。
关闭值为nil的chan
package main
// 关闭一个nil通道
func main() {
var nilChan chan int
// 运行时错误:panic: close of nil channel
close(nilChan)
}
输出:panic: close of nil channel
重复关闭同一个通道
package main
// 关闭一个重复的通道
func main() {
sameChan := make(chan int, 1)
// 关闭通道
close(sameChan)
// 运行时错误:panic: close of closed channel
// 重复关闭同一个通道
close(sameChan)
}
输出:panic: close of closed channel
关闭只读通道
package main
// 关闭一个只读通道
func main() {
onlyReadChan := make(<-chan int, 1)
// 编译错误: invalid operation: cannot close receive-only channel onlyReadChan (variable of type <-chan int)
close(onlyReadChan)
}
编译错误,输出:invalid operation: cannot close receive-only channel onlyReadChan (variable of type <-chan int)
协程使用场景
1.传递数据
通道最基本的用法就是传递数据。在Go语言中,通道是一种可以在多个Go协程之间传递数据的管道。通道可以是带缓冲的或者不带缓冲的。不带缓冲的通道是同步的,发送和接收操作必须同时准备好才能继续执行。带缓冲的通道是异步的,发送和接收操作可以独立执行,只有在缓冲区满或者空的时候才会阻塞。
下面是一个简单的例子,展示了如何使用通道传递数据:
package main
import "fmt"
func doTransfer(transfer chan<- int) {
fmt.Println("start transfer: ")
// 向通道写入数据
for i := 1; i <= 5; i++ {
fmt.Println("写入数据:", i)
transfer <- i
}
// 传输完毕, 关闭
close(transfer)
}
func main() {
var transfer chan int
transfer = make(chan int, 10)
// 开始接受数据
go doTransfer(transfer)
// 接收数据
for i := range transfer {
fmt.Println("接收到数据: ", i)
}
fmt.Println("!!!程序退出!!!")
}
2.并发控制
以爬虫为例,比如需要爬取1W条数据,需要并发爬取提高效率,但是并发数有不能太大,可以通过chan
来控,比如同时支持5个任务。
package main
func getUrl(url string) {
}
func main() {
ch := make(chan int, 5)
urls := []string{"url2", "url1"}
for _, url := range urls {
url := url
go func() {
ch <- 1
go getUrl(url)
<-ch
}()
}
}
3.信号广播
通道还可以用于在多个Go协程之间广播信号。当我们需要向多个协程发送一个信号,让它们同时开始执行某个操作时,可以使用通道来实现。下面是一个例子,展示了如何使用通道进行信号广播:
package main
import (
"fmt"
"sync"
)
func doWorker(id int, done chan bool, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("worker %d starting\n", id)
// 等待接收done通道的信号
<-done
fmt.Printf("worker %d done\n", id)
}
func main() {
var wg sync.WaitGroup
done := make(chan bool)
for i := 1; i <= 5; i++ {
wg.Add(1)
go doWorker(i, done, &wg)
}
// 向所有协程发送信号,让它们开始执行
close(done)
wg.Wait()
fmt.Println("all workers done")
}
在上面的例子中,我们创建了一个通道done用来广播信号,然后启动了五个Go协程,在协程中等待接收done通道的信号。在主函数中,我们向done通道发送了一个信号,让所有协程开始执行任务。协程在完成任务后,会向WaitGroup对象发送一个Done信号,最后我们通过调用Wait方法等待所有协程的完成信号,保证所有协程都已经执行完毕。