12.1 通道-使用通道、带缓冲的通道

2024-05-28 12:28
文章标签 使用 缓冲 通道 12.1

本文主要是介绍12.1 通道-使用通道、带缓冲的通道,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

使用通道

如果说Goroutine是一种支持并发的编程方式,那么通道就是一种在Goroutine之间建立通信的机制。(详细内容参见《Goroutine与MPG多线程文档》)

  • 传统意义上讲,线程之间的数据是天然共享的,并不需要为通信建立专门的机制。
  • 线程之间真正需要考虑的是如何避免针对共享数据的访问冲突,例如通过加锁,在一个线程读取共享数据时禁止其它线程修改该数据,或者相反。
  • 然而通过锁来维护数据完整性的做法并非那么简单。如果不能确知程序中哪些部分需要读取数据,哪些部分又需要修改数据,以及它们的先后顺序,那么将很难预测可能发生的情况。即便是久经沙场的编程老手,也很难避免线程间因竞争共享资源而导致的BUG。
  • 鉴于历史的教训,Go语言主张线程之间与其通过共享数据交换信息,不如建立通信机制。

除去在线程间交换数据以外,通道还有另外一个作用,即在宏观的异步过程中,借助某种形式的"停——等"逻辑,以满足微观的同步需求。

  • 某个线程执行到某个特定位置,可能需要""下来,""另一个线程完成特定的操作后再继续执行,例如消费者线程必须等待生产者线程为其创造可供消费的资源才能继续消费。
  • 传统意义上的线程同步,往往需要借助于互斥锁信号量条件变量等内核机制来实现。
  • Go语言利用通道的阻塞I/O,在语言层面,以极简单的方式为异步线程提供了同步支持。

创建通道可使用内置函数make,收发消息可使用<-符号

  • c := make(chan string) // 创建一个用于传输字符串数据的通道
  • c <- "Task A of step 1 finished" // 向通道发送(写入)消息(数据)
  • message := <-c // 从通道接收(读取)消息(数据)
// 并发中的异步问题
// 完全并发的"线程"以异步模式运行,其先后顺序完全不受控制
//
// step1和step2是一个任务的两步,需顺序执行,且step1是step2的前置条件;
// Task A 和Task B是step1的两个可并发执行的子任务。
//       Task A of step 1
// +--------------------------+
// |                          |
// +--------+--------+--------+--------+
// 0        1        2        3        4
//
//  Task B of step 1
// +-----------------+
// |                 |
// +--------+--------+--------+--------+
// 0        1        2        3        4
//
//                         Step 2
//                   +-----------------+
//                   |                 |
// +--------+--------+--------+--------+
// 0        1        2        3        4
package mainimport ("fmt""time"
)func step1a() {fmt.Println("Task A of step 1 start...")time.Sleep(time.Second * 3)fmt.Println("Task A of step 1 finished")
}func step1b() {fmt.Println("Task B of step 1 start...")time.Sleep(time.Second * 2)fmt.Println("Task B of step 1 finished")
}func step2() {fmt.Println("Step 2 start...")time.Sleep(time.Second * 2)fmt.Println("Step 2 finished")
}func main() {go step1a()step1b()step2()
}
// 打印输出:
// Task B of step 1 start...
// Task A of step 1 start...
// Task B of step 1 finished
// Step 2 start...
// Task A of step 1 finished
// Step 2 finished 
// 从上述输出可知,程序执行流程存在问题,step2在setep1完成之前就执行了。
// 并发中的同步协调
// 通过收发消息,以推送方式协调并发事件,使执行过程更加有序
//
//       Task A of step 1
// +--------------------------+
// |                          |
// +--------+--------+--------+--------+--------+
// 0        1        2        3        4        5
//
//  Task B of step 1
// +-----------------+
// |                 |
// +--------+--------+--------+--------+--------+
// 0        1        2        3        4        5
//
//                                  Step 2
//                            +-----------------+
//                            |                 |
// +--------+--------+--------+--------+--------+
// 0        1        2        3        4        5 // 通道让数据能够进入或离开"线程",以在"线程"之间建立通信
// 
// +---------------+           +----------------+ 
// |   GOROUTINE   |<-CHANNEL->|   GOROUTINE    |
// +---------------+           +----------------+
//
// 创建通道:通道 := make(chan 消息类型) 
// 发送消息:通道 <- 消息
// 接收消息:消息 = <-通道
package main
import ("fmt""time"
)
// 通道可以作为函数的参数被传递
// 读写通道:通道 chan 消息类型
// 只读通道:通道 <-chan 消息类型
// 只写通道:通道 chan<- 消息类型func step1a(c chan string) {fmt.Println("Task A of step 1 start...")time.Sleep(time.Second * 3)c <- "Task A of step 1 finished"//channel c的写入操作,会解阻塞c的可读
}
func step1b() {fmt.Println("Task B of step 1 start...")time.Sleep(time.Second * 2)fmt.Println("Task B of step 1 finished")
}
func step2() {fmt.Println("Step 2 start...")time.Sleep(time.Second * 2)fmt.Println("Step 2 finished")
} func main() {c := make(chan string)go step1a(c) // 将通道c作为参数传给step1astep1b()message := <-c // 当channel c为空时,读取数据是阻塞的,需要等待。因此,// 借助channel解决了同步问题fmt.Println(message)step2()
}
// 打印输出:
// Task B of step 1 start...
// Task A of step 1 start...
// Task B of step 1 finished
// Task A of step 1 finished
// Step 2 start...
// Step 2 finished 

带缓冲的通道(缓冲通道的遍历-2种方法)

 

  • 默认情况下的通道是不带缓冲区的,发送者向这样的通道发送消息,如果没有接收者前来接收,发送过程会发生阻塞。
  • 如果不希望发送过程因所发消息无人接收而阻塞,可以使用带缓冲的通道
  • 缓冲意味着可将消息缓存在通道内部,发送者不必等待,什么时候接收者准备就绪了,再从通道中将消息取走。
  • 创建带缓冲的通道,可在make函数的第二个参数中指定缓冲区的长度。
    • c := make(chan string, 2) // 该通道最多缓存两条消息,向其发送更多消息将导致错误。
  • 访问缓冲通道中的消息(2中方法)可以通过循环迭代缓冲通道中的所有消息。
    • for i := 0; i < cap(c); i++ { message := <-c }cap(c)可以返回channel c的缓冲数据个数
    • for message := range c { fmt.Println(message) } 
// 有缓冲通道
// 无缓冲通道:如果没有接收者从通道中接收消息,发送者将会阻塞等待
// 有缓冲通道:如果没有接收者从通道中接收消息,发送者不会阻塞等待
// 无论哪种通道,如果没有发送者向通道发送消息,接收者都会阻塞等待
// 创建有缓冲通道:通道 := make(chan 消息类型, 消息数)
package main
import ("fmt""time"
)func step1a(c chan string) {fmt.Println("Task A of step 1 start...")time.Sleep(time.Second * 3)c <- "Task A of step 1 finished" // task a写入完成信息
} func step1b(c chan string) {fmt.Println("Task B of step 1 start...")time.Sleep(time.Second * 2)c <- "Task B of step 1 finished" // task b写入完成信息
}
func step2() {fmt.Println("Step 2 start...")time.Sleep(time.Second * 2)fmt.Println("Step 2 finished")
} func main() {c := make(chan string, 2)go step1a(c)  // 执行task1ago step1b(c)  // 执行task1bfor i := 0; i < cap(c); i++ {message := <-c			// 遍历channle c,模拟确认所有前置已完成fmt.Println(message)}step2()
}
// 打印输出:
// Task B of step 1 start...
// Task A of step 1 start...
// Task B of step 1 finished
// Task A of step 1 finished
// Step 2 start...
// Step 2 finished

这篇关于12.1 通道-使用通道、带缓冲的通道的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1010560

相关文章

Spring IoC 容器的使用详解(最新整理)

《SpringIoC容器的使用详解(最新整理)》文章介绍了Spring框架中的应用分层思想与IoC容器原理,通过分层解耦业务逻辑、数据访问等模块,IoC容器利用@Component注解管理Bean... 目录1. 应用分层2. IoC 的介绍3. IoC 容器的使用3.1. bean 的存储3.2. 方法注

Python内置函数之classmethod函数使用详解

《Python内置函数之classmethod函数使用详解》:本文主要介绍Python内置函数之classmethod函数使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录1. 类方法定义与基本语法2. 类方法 vs 实例方法 vs 静态方法3. 核心特性与用法(1编程客

Linux中压缩、网络传输与系统监控工具的使用完整指南

《Linux中压缩、网络传输与系统监控工具的使用完整指南》在Linux系统管理中,压缩与传输工具是数据备份和远程协作的桥梁,而系统监控工具则是保障服务器稳定运行的眼睛,下面小编就来和大家详细介绍一下它... 目录引言一、压缩与解压:数据存储与传输的优化核心1. zip/unzip:通用压缩格式的便捷操作2.

使用Python实现可恢复式多线程下载器

《使用Python实现可恢复式多线程下载器》在数字时代,大文件下载已成为日常操作,本文将手把手教你用Python打造专业级下载器,实现断点续传,多线程加速,速度限制等功能,感兴趣的小伙伴可以了解下... 目录一、智能续传:从崩溃边缘抢救进度二、多线程加速:榨干网络带宽三、速度控制:做网络的好邻居四、终端交互

Python中注释使用方法举例详解

《Python中注释使用方法举例详解》在Python编程语言中注释是必不可少的一部分,它有助于提高代码的可读性和维护性,:本文主要介绍Python中注释使用方法的相关资料,需要的朋友可以参考下... 目录一、前言二、什么是注释?示例:三、单行注释语法:以 China编程# 开头,后面的内容为注释内容示例:示例:四

Go语言数据库编程GORM 的基本使用详解

《Go语言数据库编程GORM的基本使用详解》GORM是Go语言流行的ORM框架,封装database/sql,支持自动迁移、关联、事务等,提供CRUD、条件查询、钩子函数、日志等功能,简化数据库操作... 目录一、安装与初始化1. 安装 GORM 及数据库驱动2. 建立数据库连接二、定义模型结构体三、自动迁

ModelMapper基本使用和常见场景示例详解

《ModelMapper基本使用和常见场景示例详解》ModelMapper是Java对象映射库,支持自动映射、自定义规则、集合转换及高级配置(如匹配策略、转换器),可集成SpringBoot,减少样板... 目录1. 添加依赖2. 基本用法示例:简单对象映射3. 自定义映射规则4. 集合映射5. 高级配置匹

Spring 框架之Springfox使用详解

《Spring框架之Springfox使用详解》Springfox是Spring框架的API文档工具,集成Swagger规范,自动生成文档并支持多语言/版本,模块化设计便于扩展,但存在版本兼容性、性... 目录核心功能工作原理模块化设计使用示例注意事项优缺点优点缺点总结适用场景建议总结Springfox 是

嵌入式数据库SQLite 3配置使用讲解

《嵌入式数据库SQLite3配置使用讲解》本文强调嵌入式项目中SQLite3数据库的重要性,因其零配置、轻量级、跨平台及事务处理特性,可保障数据溯源与责任明确,详细讲解安装配置、基础语法及SQLit... 目录0、惨痛教训1、SQLite3环境配置(1)、下载安装SQLite库(2)、解压下载的文件(3)、

使用Python绘制3D堆叠条形图全解析

《使用Python绘制3D堆叠条形图全解析》在数据可视化的工具箱里,3D图表总能带来眼前一亮的效果,本文就来和大家聊聊如何使用Python实现绘制3D堆叠条形图,感兴趣的小伙伴可以了解下... 目录为什么选择 3D 堆叠条形图代码实现:从数据到 3D 世界的搭建核心代码逐行解析细节优化应用场景:3D 堆叠图