目录
在 Go 语言中,close
函数用于关闭一个通道,表明没有更多的数据将被发送到这个通道。这是一个重要的操作,因为它可以帮助接收方理解发送方何时停止发送数据,从而可以安全地停止从通道接收更多数据。关闭通道后,通道中已存在的数据仍然可以被接收,但不能再向该通道发送数据。
代码实例
// _Closing_ a channel indicates that no more values
// will be sent on it. This can be useful to communicate
// completion to the channel's receivers.
package main
import "fmt"
// In this example we'll use a `jobs` channel to
// communicate work to be done from the `main()` goroutine
// to a worker goroutine. When we have no more jobs for
// the worker we'll `close` the `jobs` channel.
func main() {
jobs := make(chan int, 5)
done := make(chan bool)
// Here's the worker goroutine. It repeatedly receives
// from `jobs` with `j, more := <-jobs`. In this
// special 2-value form of receive, the `more` value
// will be `false` if `jobs` has been `close`d and all
// values in the channel have already been received.
// We use this to notify on `done` when we've worked
// all our jobs.
go func() {
for {
j, more := <-jobs
if more {
fmt.Println("received job", j)
} else {
fmt.Println("received all jobs")
done <- true
return
}
}
}()
// This sends 3 jobs to the worker over the `jobs`
// channel, then closes it.
for j := 1; j <= 2; j++ {
jobs <- j
fmt.Println("sent job", j)
}
close(jobs)
fmt.Println("sent all jobs")
// We await the worker using the
// [synchronization](channel-synchronization) approach
// we saw earlier.
<-done
// Reading from a closed channel succeeds immediately,
// returning the zero value of the underlying type.
// The optional second return value is `true` if the
// value received was delivered by a successful send
// operation to the channel, or `false` if it was a
// zero value generated because the channel is closed
// and empty.
_, ok := <-jobs
fmt.Println("received more jobs:", ok)
}
关闭通道的逻辑
jobs
通道用来从 main
协程向工作协程发送工作任务。当所有的工作都发送完毕后,通过 close(jobs)
来关闭 jobs
通道,表示没有更多的工作会被发送。
如何安全地关闭通道
关闭通道应当由发送方执行,且只能执行一次。如果多次关闭同一个通道,或者在通道已关闭后再发送数据,程序将引发运行时恐慌(panic)。因此,确保关闭通道的操作只在确信所有数据发送完毕后执行,并且由单一的发送方协程来处理,是非常重要的。
使用 more
变量判断通道状态
代码中工作协程使用 for
循环通过 j, more := <-jobs
从 jobs
通道接收数据。这里的 more
是一个布尔值,如果能从通道接收到数据,more
为 true
;如果通道被关闭并且所有数据都已经接收完毕,more
则为 false
。这允许工作协程知道何时停止等待新的工作,因为它通过 more
的状态了解到通道已经关闭。
结束工作的通知
当 more
变量为 false
时,工作协程通过发送 true
到 done
通道来通知 main
协程所有的工作都已经处理完毕,随后返回结束自身的执行。
通道关闭后的行为
关闭通道后,尝试从通道接收仍然是可能的。如果通道中仍有未被接收的数据,这些数据仍然可以被接收。一旦所有数据都被接收,任何进一步的接收尝试都会立即返回通道元素类型的零值,more
变量将返回 false
,表明没有更多的数据。
通过这种机制,Go 语言的通道和协程间同步为并发编程提供了强大而安全的工具。练习和理解如何正确使用通道和协程,对于开发高效、健壮的并发程序至关重要。
示例一:任务分发和完成通知
在 Go 语言中,关闭通道的使用场景非常多,它是协程间通信过程中一个重要的同步手段。下面我将提供两个实用的例子,展示在不同场景下如何正确地使用关闭通道的功能。
在这个例子中,我们将使用一个通道来分发任务给多个工作协程,并在所有任务完成后进行通知。
package main
import (
"fmt"
"sync"
)
func worker(id int, jobs <-chan int, wg *sync.WaitGroup) {
for j := range jobs {
fmt.Printf("Worker %d processing job %d\n", id, j)
wg.Done()
}
fmt.Printf("Worker %d finished all jobs\n", id)
}
func main() {
const numJobs = 5
jobs := make(chan int, numJobs)
var wg sync.WaitGroup
// 创建3个工作协程
for w := 1; w <= 3; w++ {
go worker(w, jobs, &wg)
}
// 发布任务
for j := 1; j <= numJobs; j++ {
jobs <- j
wg.Add(1)
}
close(jobs) // 发布完任务后关闭通道
wg.Wait() // 等待所有任务完成
fmt.Println("All jobs are completed")
}
在这个例子中,我们创建了一个jobs
通道分发任务给工作协程,并使用sync.WaitGroup
来等待所有任务完成。通道被关闭来告知工作协程不会有新的任务发送。
示例二:生成者-消费者模式
我们将展示一个基础的生成者-消费者模式,其中生成者协程产生数据,消费者协程处理数据。
package main
import (
"fmt"
"time"
)
func producer(ch chan<- int) {
for i := 0; i < 10; i++ {
fmt.Printf("Produced: %d\n", i)
ch <- i
time.Sleep(time.Second) // 模拟耗时的生产过程
}
close(ch) // 生产完毕,关闭通道
}
func consumer(ch <-chan int) {
for i := range ch {
fmt.Printf("Consumed: %d\n", i)
}
fmt.Println("All items have been consumed.")
}
func main() {
ch := make(chan int)
go producer(ch) // 启动生产者协程
consumer(ch) // 在主协程中消费数据
}
在这个例子中,producer
协程负责生产数据并发送到通道 ch
中。当所有数据生产完毕后,它关闭了通道。consumer
函数通过从通道接收数据来消费它,一旦通道关闭,for
循环就会结束。