目录

代码实例

关闭通道的逻辑

如何安全地关闭通道

使用 more 变量判断通道状态

结束工作的通知

通道关闭后的行为

示例一:任务分发和完成通知

示例二:生成者-消费者模式


        在 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 := <-jobsjobs 通道接收数据。这里的 more 是一个布尔值,如果能从通道接收到数据,moretrue;如果通道被关闭并且所有数据都已经接收完毕,more 则为 false。这允许工作协程知道何时停止等待新的工作,因为它通过 more 的状态了解到通道已经关闭。

结束工作的通知

        当 more 变量为 false 时,工作协程通过发送 truedone 通道来通知 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 循环就会结束。

04-29 21:11