当前位置: 首页 > article >正文

【魅力golang】之-通道

昨天发布了golang的最大特色之一--协程,与协程密不可分的是通道(channel),用来充当协程间相互通信的角色。通道是一种内置的数据结构,所以才造就了golang强大的并发能力。今天风云来爬一爬通道的详细用法。

通道在golang中也叫协程间通信原语。通过通道,协程可以安全、同步地交换数据,避免直接操作共享内存带来的复杂性。

1、通道的特征:

类型化:通道是一个类型化的管道,数据只能按特定类型传递。

阻塞同步发送操作如果通道满了,发送方会阻塞,直到有协程接收数据。接收操作如果通道为空,接收方会阻塞,直到有数据写入。

无锁并发:通道内部实现了无锁队列和条件变量,确保高效的并发操作。

2、通道的设计思路

基于 CSP 模型:Go 的通道实现了 Communicating Sequential Processes(CSP) 并发模型,将共享内存的问题转化为通信问题。

核心思想:“不要通过共享内存来通信,而是通过通信来共享内存。”

3、通道的作用

  • 提供一种安全的协程间通信机制。
  • 避免复杂的加锁操作。
  • 支持同步(无缓冲)和异步(缓冲)通信。
  • 实现协程间的消息队列、信号传递、负载均衡等功能。

4、 通道的用法

4.1 创建通道

  • 使用 make 创建通道。
  • 语法:ch := make(chan T),其中 T 是通道中数据的类型。

示例:创建无缓冲通道

package main

import "fmt"

func main() {
	ch := make(chan int) // 无缓冲通道

	fmt.Println("Channel created:", ch)
}

4.2 通道的基本操作

  1. 发送数据:通过 ch <- value 向通道发送数据。
  2. 接收数据:通过 value := <-ch 从通道接收数据。
  3. 关闭通道:使用 close(ch) 关闭通道,表示不会再发送数据。

示例:基本发送与接收

package main

import "fmt"

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

	// 启动一个协程发送数据
	go func() {
		ch <- 42 // 发送一个整数值
	}()

	// 接收数据
	value := <-ch

	fmt.Println("Received value:", value) // 输出:Received value: 42
}

4.3 无缓冲通道

  • 无缓冲通道的发送和接收操作是同步的。
  • 发送方和接收方必须同时准备好,操作才能完成。

示例:无缓冲通道的阻塞特性

package main

import (
	"fmt"
)

func main() {
	ch := make(chan string)  // 创建一个无缓冲的通道

	go func() {
		fmt.Println("Sending message...")

		ch <- "Hello, Channel!" // 发送方阻塞,直到接收方准备好

		fmt.Println("Message sent!")
	}()

	message := <-ch // 接收操作
	fmt.Println("Received message:", message)  // 接收方阻塞,直到发送方发送消息
}

输出:

Sending message...
Message sent!
Received message: Hello, Channel!

4.4 缓冲通道

  • 缓冲通道允许存储固定数量的数据,发送方只有在缓冲区满时才会阻塞。

示例:缓冲通道

package main

import (
	"fmt"
)

func main() {
	ch := make(chan int, 3) // 创建一个容量为 3 的缓冲通道

	ch <- 1  // 发送数据
	ch <- 2  
	ch <- 3

	fmt.Println("All values sent")

	fmt.Println(<-ch) // 接收数据
	fmt.Println(<-ch)
	fmt.Println(<-ch)
}

输出:

All values sent
1
2
3

4.5 单向通道

  • 单向通道用于限制通道的操作。
    • 只发送:chan<- T
    • 只接收:<-chan T

示例:单向通道

package main

import "fmt"

func sendOnly(ch chan<- int) {
	ch <- 42 // 发送值

	fmt.Println("Value sent")
}

func receiveOnly(ch <-chan int) {
	value := <-ch // 接收值

	fmt.Println("Value received:", value) // 这里会执行
}

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

	go sendOnly(ch) //发送值

	receiveOnly(ch) //接收值
}

输出:

Value sent
Value received: 42

4.6 通道的关闭

  • 使用 close(ch) 关闭通道。
  • 接收数据时,使用 v, ok := <-ch 检查通道是否已关闭。

示例:关闭通道

package main

import "fmt"

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

	go func() {
		for i := 0; i < 5; i++ {

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

		}

		close(ch) // 关闭通道
	}()

	for v := range ch { // 使用 range 遍历通道
		fmt.Println(v) // 输出接收到的数据
	}

	fmt.Println("Channel closed")
}

输出:

0
1
2
3
4
Channel closed

5、 通道的应用场景

5.1 消息队列

通道可以实现简单的消息队列,用于协程间的任务分发。

示例:通道实现消息队列

package main

import (
	"fmt"

	"time"
)

func worker(id int, tasks <-chan int) {
	for task := range tasks {
		fmt.Printf("Worker %d processing task %d\n", id, task)

		time.Sleep(500 * time.Millisecond) // 模拟任务耗时
	}
}

func main() {
	tasks := make(chan int, 10)

	// 启动多个协程
	for i := 1; i <= 3; i++ {
		go worker(i, tasks)
	}

	// 向通道发送任务
	for i := 1; i <= 10; i++ {
		tasks <- i
	}

	close(tasks) // 关闭通道,表示不再发送任务

	time.Sleep(3 * time.Second) // 等待所有任务完成
}

输出:

Worker 3 processing task 2
Worker 2 processing task 3
Worker 1 processing task 1
Worker 1 processing task 4
Worker 2 processing task 6
Worker 3 processing task 5
Worker 2 processing task 7
Worker 1 processing task 8
Worker 3 processing task 9
Worker 3 processing task 10

5.2 协程通信

通道用于协程间的安全通信,避免使用共享变量。

示例:通道实现协程通信

package main

import (
	"fmt"

	"sync"
)

func worker(id int, ch chan int, wg *sync.WaitGroup) {
	defer wg.Done() // 减少计数器

	for num := range ch { // 从通道中接收数据
		fmt.Printf("Worker %d received: %d\n", id, num)
	}
}

func main() {
	var wg sync.WaitGroup // 用于等待所有协程完成

	ch := make(chan int) // 定义一个通道

	// 启动协程
	for i := 1; i <= 3; i++ { // 启动3个协程
		wg.Add(1) // 增加计数器

		go worker(i, ch, &wg) // 启动协程
	}

	// 发送数据
	for i := 1; i <= 10; i++ { // 发送10个数据
		ch <- i // 将数据发送到通道
	}

	close(ch) // 关闭通道,通知协程退出

	wg.Wait() // 等待所有协程完成

	fmt.Println("All workers finished")
}

输出:

Worker 3 received: 1
Worker 3 received: 4
Worker 3 received: 5
Worker 3 received: 6
Worker 3 received: 7
Worker 3 received: 8
Worker 3 received: 9
Worker 2 received: 3
Worker 1 received: 2
Worker 3 received: 10
All workers finished

5.3 定时器和信号通知

通道结合 time 包可以实现超时控制和信号通知。

示例:超时控制

package main

import (
	"fmt"

	"time"
)

func main() {
	ch := make(chan int) // 无缓冲

	go func() {
		time.Sleep(2 * time.Second) // 模拟耗时

		ch <- 42 // 发送数据
	}()

	select { // 等待接收数据或超时
	case value := <-ch: // 接收数据

		fmt.Println("Received value:", value)

	case <-time.After(1 * time.Second): // 超时处理

		fmt.Println("Timeout")
	}
}

输出:Timeout

6、注意事项

避免写入关闭的通道:对已关闭的通道写入数据会导致 panic。

通道的死锁问题:如果所有接收者都退出,而发送者仍在发送,会导致死锁。

容量规划:合理规划缓冲通道的容量,避免频繁阻塞。

数据泄露:确保通道关闭后协程正确退出,避免资源泄露。

Golang 中的通道是实现协程间通信的核心工具,简化了并发编程的复杂性。通道通过 CSP 模型实现协程间的数据传递,在消息队列、协程同步、超时控制等场景中表现出色。合理使用通道能有效提高程序的并发能力和可维护性。


http://www.kler.cn/a/451254.html

相关文章:

  • Unity中如何实现绘制Sin函数图像
  • whisper.cpp: Android端测试 -- Android端手机部署音频大模型
  • 独一无二,万字详谈——Linux之文件管理
  • 虚幻引擎结构之UWorld
  • 16.1、网络安全风险评估过程
  • 基于Spring Boot的九州美食城商户一体化系统
  • HTML+CSS+JS制作外贸网站(内附源码,含5个页面)
  • 3.学习webpack配置 尝试打包ts文件
  • 【Git】-- 版本说明
  • 每天40分玩转Django:Django国际化
  • 【Kibana01】企业级日志分析系统ELK之Kibana的安装与介绍
  • 学习一下USB DFU
  • AI驱动的数据分析:利用自然语言实现数据查询到可视化呈现
  • 2.在 Vue 3 中使用 ECharts 实现动态时间轴效果
  • Android Studio打开一个外部的Android app程序
  • embeding 层到底是什么
  • YOLOv8 引入高效的可变形卷积网络 DCNv4 | 重新思考用于视觉应用的动态和稀疏算子
  • 【hackmyvm】BlackWidow靶机wp
  • MongoDB教程002:文档(表)的增删改查
  • 如何在防火墙上指定ip访问服务器上任何端口呢