» Go语言快速入门 » 3. 高级篇 » 3.2 Channels 通道

Channels 通道

不要通过共享内存进行通信;相反,通过通信共享内存

通道是一种强大的并发原语,允许在 goroutine 之间进行通信和同步。

你可以使用 make 函数创建一个通道。 默认情况下,通道是无缓冲的。这意味着它只会在有一个相应的接收 (<- chan) 准备好接收值的情况下才会允许发送 (chan <-)。

ch := make(chan int) // 创建一个类型为int的无缓冲通道

ch2 := make(chan int, 3) // 带有容量为3的缓冲通道

缓冲

缓冲通道可在没有接收器的情况下发送一定数量的值。

package main

import "fmt"

func main() {

    messages := make(chan string, 2)

    messages <- "lite"
    messages <- "rank"

    fmt.Println(<-messages)
    fmt.Println(<-messages)
}

同步

package main

import (
    "fmt"
    "time"
)

func worker(done chan int) {
    fmt.Print("working...")
    time.Sleep(time.Second)
    fmt.Println("done")

    done <- 1
}

func main() {
    done := make(chan int, 1)
    go worker(done)

    <-done
}

worker 函数模拟一些工作,并将值发送到 done 通道来通知工作完成。

方向

通道可以有方向,这意味着开发者可以指定通道是仅用于发送还是仅用于接收值。 这是一种在语言层级防止通道误用的类型检查形式。通道方向使用 <- 操作符声明。

只发送通道

package main

import "fmt"

func sendData(sendCh chan<- int) {
    sendCh <- 42
}

func main() {
    ch := make(chan int)
    go sendData(ch)

    fmt.Println("Data sent successfully")
}

只接收通道

package main

import "fmt"

func receiveData(receiveCh <-chan int) {
    data := <-receiveCh
    fmt.Println("Received data:", data)
}

func main() {
    ch := make(chan int)
    go receiveData(ch)

    fmt.Println("Data received successfully")
}

关闭

你可以关闭通道以表示不会再发送任何值。关闭通道可通知接收方停止等待新值。

package main

import (
	"fmt"
	"time"
)

func produceData(dataCh chan<- int) {
	for i := 1; i <= 5; i++ {
		dataCh <- i
	}
	close(dataCh)
}

func consumeData(dataCh <-chan int, done chan<- struct{}) {
	for {
		// 如果通道已关闭,则'ok'值将为false。
		data, ok := <-dataCh

		if !ok {
			fmt.Println("Channel closed. No more data.")
			close(done) // 通知消费完成
			return
		}

		fmt.Println("Received data:", data)
		time.Sleep(time.Second)
	}
}

func main() {
	dataCh := make(chan int)
	done := make(chan struct{})

	go produceData(dataCh)

	go consumeData(dataCh, done)

	// 阻塞,直到'done'通道关闭,表示消费完成。
	<-done

	fmt.Println("Main goroutine exiting.")
}

迭代

for ... range 循环通常与通道一起使用,以在通道关闭之前迭代值。

package main

import (
	"fmt"
	"time"
)

func produceData(dataCh chan<- int) {
	for i := 1; i <= 5; i++ {
		dataCh <- i
	}
	close(dataCh)
}

func consumeData(dataCh <-chan int, done chan<- struct{}) {
	for data := range dataCh {
		fmt.Println("Received data:", data)
		time.Sleep(time.Second) // 模拟一些处理
	}

	// 通道已关闭,通知消费完成
	close(done)
}

func main() {
	dataCh := make(chan int)
	done := make(chan struct{})

	go produceData(dataCh)
	go consumeData(dataCh, done)

	// 阻塞,直到'done'通道关闭,表示消费完成。
	<-done

	fmt.Println("Main goroutine exiting.")
}

非阻塞操作

你可以使用 select 语句在通道上执行非阻塞操作。 select 语句允许你同时等待多个通信操作,然后选择执行第一个可以执行的操作。

package main

import "fmt"

func main() {
	dataCh := make(chan int, 1)

  dataCh <- 58

	// 尝试在不阻塞的情况下向通道发送值。
	select {
	case dataCh <- 42:
		fmt.Println("Data sent successfully.")
	default:
		fmt.Println("Unable to send data. Channel is full.")
	}
    // => Unable to send data. Channel is full.

	// 尝试在不阻塞的情况下从通道接收值。
	select {
	case data := <-dataCh:
		fmt.Println("Received data:", data)
	default:
		fmt.Println("No data available. Channel is empty.")
	}
    // => Received data: 58

	close(dataCh)
}

协程池

协程池是一种常见的并发模式,其使用固定数量的工作 goroutine 来处理来自队列的任务。 通道通常用于主 goroutine(生成任务的地方)和工作 goroutine 之间通信。

package main

import "fmt"

func worker(id int, jobs <-chan int, results chan<- int, done chan<- struct{}) {
	for job := range jobs {
		fmt.Printf("Worker %d processing job %d\n", id, job)
		results <- job * 2
	}

	// 通知已完成其工作
	done <- struct{}{}
}

func main() {
	const numJobs = 10
	const numWorkers = 3

	// 为主goroutine和工作goroutine之间的通信创建通道。
	jobs := make(chan int, numJobs)
	results := make(chan int, numJobs)
	done := make(chan struct{}, numWorkers)

	for i := 1; i <= numWorkers; i++ {
		go worker(i, jobs, results, done)
	}

	// 生成任务并将它们发送到jobs通道。
	go func() {
		for i := 1; i <= numJobs; i++ {
			jobs <- i
		}
		close(jobs)
	}()

	// 等待所有工作goroutine完成。
	for i := 1; i <= numWorkers; i++ {
		<-done
	}

	close(results)

	for result := range results {
		fmt.Printf("Result: %d\n", result)
	}
}

代码挑战

写一个 Go 程序,以并发方式计算字符串中单词的出现次数。该程序应使用 goroutine,并使用通道来传递结果。

Loading...
> 此处输出代码运行结果
上页
下页