go中的 chan 的使用

84 阅读4分钟

在Go语言中,chan(通道)是一种用于在协程之间进行通信的机制。通道可以在协程之间传递数据,实现协程间的同步和通信。

创建通道:

// 创建一个整型通道
ch := make(chan int)

// 创建一个字符串通道,缓冲区大小为 10
ch := make(chan string, 10)

发送数据到通道:

// 向通道发送数据
ch <- value

接收数据从通道:

// 从通道接收数据
value := <-ch

关闭通道:

// 关闭通道
close(ch)

示例:

package main

import (
	"fmt"
	"time"
)

func sender(ch chan int) {
	for i := 0; i < 5; i++ {
		ch <- i // 发送数据到通道
		time.Sleep(time.Second)
	}
	close(ch) // 关闭通道
}

func receiver(ch chan int) {
	for {
		value, ok := <-ch // 从通道接收数据
		if !ok {
			fmt.Println("Channel closed")
			break
		}
		fmt.Println("Received:", value)
	}
}

func main() {
	ch := make(chan int) // 创建一个整型通道

	go sender(ch)    // 启动发送者协程
	go receiver(ch)  // 启动接收者协程

	time.Sleep(time.Second * 6) // 等待一段时间确保协程执行完毕
}

在这个示例中,我们创建了一个整型通道,并启动了发送者协程和接收者协程。发送者协程循环发送数据到通道,并在发送完成后关闭通道。接收者协程循环接收数据,并在通道关闭后退出循环。最后,主协程等待一段时间确保协程执行完毕。

协程之间的通信和同步

通道在Go语言中被广泛用于协程之间的通信和同步。以下是一些常见的使用场景:

  1. 并发任务的结果收集:在并发执行多个任务的情况下,可以使用通道来收集每个任务的结果,并在所有任务执行完成后进行汇总或处理。

  2. 生产者-消费者模型:通道可以用作生产者和消费者之间的消息队列。生产者将数据发送到通道,消费者从通道接收数据并进行处理,从而实现了生产者和消费者的解耦和并发处理。

  3. 限制并发度:通过带缓冲的通道,可以限制并发执行的协程数量,防止系统资源被过度消耗。例如,可以使用一个有限大小的通道作为信号量来控制并发执行的协程数量。

  4. 事件驱动编程:通道可以用作事件的发布和订阅机制,不同的协程可以通过通道来发送和接收事件,实现了松耦合的事件驱动编程模式。

  5. 协程间的同步和通信:通道可以用于协程之间的同步和通信,例如等待子协程执行完成、等待多个协程同时完成某个阶段的任务等。

  6. 任务分发与处理:可以使用通道来分发任务给多个工作者协程,并通过通道将任务结果发送回主协程进行汇总或处理。

总的来说,通道在Go语言中是一种非常强大和灵活的并发编程工具,可以用于各种场景下协程之间的通信、同步和协作。

使用场景

以下是几个具体的使用场景,并且每个场景都附有相应的代码示例:

场景一:并发任务的结果收集

package main

import (
	"fmt"
	"sync"
	"time"
)

func worker(id int, results chan int, wg *sync.WaitGroup) {
	defer wg.Done()
	time.Sleep(time.Second)
	results <- id * id // 将结果发送到通道
}

func main() {
	var wg sync.WaitGroup
	results := make(chan int, 5) // 创建一个结果通道,缓冲区大小为 5

	for i := 0; i < 5; i++ {
		wg.Add(1)
		go worker(i, results, &wg) // 启动工作者协程
	}

	wg.Wait() // 等待所有工作者协程执行完成
	close(results) // 关闭结果通道

	// 遍历结果通道,收集结果
	for result := range results {
		fmt.Println("Result:", result)
	}
}

场景二:生产者-消费者模型

package main

import (
	"fmt"
	"time"
)

func producer(ch chan int) {
	for i := 0; i < 5; i++ {
		ch <- i // 发送数据到通道
		time.Sleep(time.Second)
	}
	close(ch) // 关闭通道
}

func consumer(ch chan int) {
	for value := range ch {
		fmt.Println("Received:", value) // 从通道接收数据
	}
}

func main() {
	ch := make(chan int) // 创建一个整型通道

	go producer(ch) // 启动生产者协程
	consumer(ch)    // 启动消费者协程
}

场景三:限制并发度

package main

import (
	"fmt"
	"sync"
	"time"
)

func worker(id int, sem chan struct{}, wg *sync.WaitGroup) {
	defer wg.Done()
	sem <- struct{}{} // 获取信号量
	defer func() {
		<-sem // 释放信号量
	}()
	fmt.Printf("Worker %d starting\n", id)
	time.Sleep(time.Second)
	fmt.Printf("Worker %d done\n", id)
}

func main() {
	sem := make(chan struct{}, 2) // 创建一个信号量,限制并发度为 2
	var wg sync.WaitGroup

	for i := 0; i < 5; i++ {
		wg.Add(1)
		go worker(i, sem, &wg) // 启动工作者协程
	}

	wg.Wait() // 等待所有工作者协程执行完成
}

在这些示例中,我们分别展示了通道在不同场景下的使用方法:

  • 场景一中,我们使用通道收集多个协程的执行结果。
  • 场景二中,我们展示了生产者-消费者模型,其中一个协程负责生产数据,另一个协程负责消费数据。
  • 场景三中,我们使用信号量限制并发度,确保同时执行的协程数量不超过设定的阈值。