新闻中心
使用Go Channel实现并发临界区的严格交替执行

本文探讨了如何在go语言中,利用双通道(dual channel)机制,确保多个并发协程(goroutines)中的临界区(critical sections)严格按照预设的顺序交替执行。通过为每个协程分配一个接收通道和一个发送通道,实现了一种令牌传递模式,有效解决了并发资源访问的同步问题,并展示了该模式的实现细节及其可扩展性。
在Go语言的并发编程中,我们经常会遇到需要多个协程访问共享资源(即临界区)的情况。通常,我们会使用互斥锁(sync.Mutex)来保证临界区在某一时刻只有一个协程在执行。然而,在某些特定场景下,我们可能需要更精细的控制,例如要求两个或多个临界区严格按照特定的顺序交替执行:CS1 -> CS2 -> CS1 -> CS2,依此类推。本文将介绍一种利用Go语言通道(channel)的强大特性,实现这种严格交替执行模式的有效方法。
问题描述
假设我们有两个并发运行的Go协程 f1 和 f2,它们各自包含一个临界区(CS1和CS2):
func f1() {
// ... some code
// critical section 1 (CS1)
// ... critical section code
// end critical section 1
// ... more code
}
func f2() {
// ... some code
// critical section 2 (CS2)
// ... critical section code
// end critical section 2
// ... more code
}我们的目标是确保 CS1 只能在 CS2 执行之后执行,反之亦然,形成一个严格的 CS1, CS2, CS1, CS2... 的交替序列。
双通道机制:核心思想
为了实现这种严格的交替执行,我们可以采用一种“令牌传递”的模式,其中Go通道充当了传递令牌的媒介。核心思想如下:
- 每个参与交替的协程都拥有两个通道: 一个用于接收“执行令牌”(do 通道),另一个用于发送“完成令牌”(next 通道)。
- 接收令牌: 一个协程在进入其临界区之前,会尝试从其 do 通道接收一个令牌。如果通道为空,它将阻塞,直到有令牌可用。
- 发送令牌: 当一个协程完成其临界区的执行后,它会将一个令牌发送到下一个协程的 do 通道中。
- 初始化: 在程序启动时,需要向第一个要执行的协程的 do 通道中发送一个初始令牌,以启动整个交替序列。
通过这种机制,令牌在协程之间循环传递,确保了只有持有令牌的协程才能进入其临界区,从而实现了严格的交替执行。
Pinokio
Pinokio是一款开源的AI浏览器,可以安装运行各种AI模型和应用
232
查看详情
实现细节与示例代码
下面是使用双通道机制实现两个协程临界区交替执行的完整Go语言示例代码。为了更好地演示,我们在临界区中加入了打印语句和短暂的延迟,并让协程持续循环执行。
package main
import (
"fmt"
"time"
)
// f1 协程负责执行临界区1
func f1(id int, do chan bool, next chan bool) {
for { // 持续循环以演示交替执行
<-do // 等待接收令牌,表示轮到f1执行
fmt.Printf("Goroutine %d: Executing Critical Section 1\n", i
d)
// --------------------------------------------------
// critical section 1 (CS1)
// 这里放置f1的临界区代码
time.Sleep(100 * time.Millisecond) // 模拟临界区工作
// end critical section 1
// --------------------------------------------------
next <- true // 完成CS1后,将令牌传递给下一个协程
}
}
// f2 协程负责执行临界区2
func f2(id int, do chan bool, next chan bool) {
for { // 持续循环以演示交替执行
<-do // 等待接收令牌,表示轮到f2执行
fmt.Printf("Goroutine %d: Executing Critical Section 2\n", id)
// --------------------------------------------------
// critical section 2 (CS2)
// 这里放置f2的临界区代码
time.Sleep(100 * time.Millisecond) // 模拟临界区工作
// end critical section 2
// --------------------------------------------------
next <- true // 完成CS2后,将令牌传递给下一个协程
}
}
func main() {
// 创建两个缓冲大小为1的通道,用于传递令牌
cf1 := make(chan bool, 1) // f1的接收通道
cf2 := make(chan bool, 1) // f2的接收通道
// 初始令牌:向cf1发送一个令牌,让f1首先开始执行
cf1 <- true
// 启动两个协程
go f1(1, cf1, cf2) // f1接收cf1的令牌,完成后将令牌发送给cf2
go f2(2, cf2, cf1) // f2接收cf2的令牌,完成后将令牌发送给cf1
// 保持主协程运行,否则程序会立即退出,无法观察到子协程的交替执行
select {} // 阻塞主协程,直到程序被手动终止
}
代码解析
-
f1 和 f2 函数:
- 这两个函数现在都接受两个 chan bool 类型的参数:do 和 next。
- 临界区代码:一旦接收到令牌,协程就可以安全地执行其临界区代码。
- next
- for {} 循环:为了实现持续的交替执行,我们将整个逻辑包裹在一个无限循环中。
- id 参数:仅用于在打印输出时区分不同的协程。
-
main 函数:
- cf1 := make(chan bool, 1) 和 cf2 := make(chan bool, 1):创建了两个带缓冲(大小为1)的布尔型通道。缓冲大小为1至关重要,它确保了令牌在通道中可以被存储一次,实现“一进一出”的传递。
- cf1
- go f1(1, cf1, cf2):启动 f1 协程,并将 cf1 作为其 do 通道(接收令牌),cf2 作为其 next 通道(发送令牌)。
- go f2(2, cf2, cf1):启动 f2 协程,并将 cf2 作为其 do 通道,cf1 作为其 next 通道。这样就形成了一个环路:f1 完成后令牌给 f2,f2 完成后令牌给 f1。
- select {}:这个语句会阻塞 main 协程,使其不会立即退出。如果没有它,main 函数会执行完毕并终止程序,导致 f1 和 f2 协程没有足够的时间运行。
注意事项与扩展性
- 通道缓冲: 使用缓冲大小为1的通道是实现令牌传递模式的关键。如果使用无缓冲通道,发送操作在没有接收者时会立即阻塞,这可能导致死锁或无法启动。缓冲为1允许令牌在发送者和接收者之间短暂地“停留”。
- 主协程阻塞: 在实际应用中,select {} 只是一个简单的阻塞 main 协程的方法。更健壮的生产级代码可能会使用 sync.WaitGroup 来等待所有协程完成,或者使用一个上下文(context.Context)来管理协程的生命周期。
- 错误处理: 示例代码未包含错误处理。在真实的临界区代码中,需要考虑可能发生的错误和异常情况,并确保令牌能够正确传递,即使临界区执行失败。
- 扩展到N个协程: 这种模式可以很容易地扩展到N个协程的交替执行。你只需要创建N个通道,并将它们连接成一个环形链表。例如,f1 将令牌传给 f2,f2 传给 f3,...,fN 传回给 f1。
- 公平性: 这种基于通道的令牌传递机制天然保证了严格的公平性,因为令牌是按顺序传递的。
总结
通过巧妙地利用Go语言的通道机制,我们可以实现对并发临界区访问的严格交替控制。双通道模式提供了一种清晰、高效且可扩展的解决方案,它将复杂的同步问题转化为简单的令牌传递逻辑。理解并掌握这种模式,将有助于开发者在Go并发编程中构建更加健壮和精确的同步机制。
以上就是使用Go Channel实现并发临界区的严格交替执行的详细内容,更多请关注其它相关文章!
# 如何在
# 网站排名优化夆还是宙d9斯推选
# 纪录片营销推广策划方案
# 关键词seo优化教程
# seo需要每年优化吗
# 现在什么网站推广比较好
# 长春网站推广视频素材
# 天津seo星宇
# 湛江建设网站建站
# 房地产优秀营销推广案例
# 微信营销朋友圈推广
# 死锁
# 道中
# go
# 布尔
# 并将
# 下一
# 双通道
# 多个
# 为其
# 令牌
# 同步机制
# 并发编程
# ai
# go语言
相关栏目:
【
科技资讯46185 】
【
网络学院92790 】
相关推荐:
解决macOS Tkinter应用双击启动崩溃:PyInstaller打包指南
Composer中的^和~符号代表什么_精通Composer版本号语义化约束
vivo云服务网页版登录 怎么登录vivo云服务网页版
J*aScript数据结构转换:将对象数组按类别分组
漫蛙网页登录入口 漫蛙漫画官方授权网址
css链接悬停下划线样式如何自定义_使用::after结合content和transition
在Typer应用中优雅地处理和重组任意命令行参数
手机屏幕碎了但能正常使用怎么办 手机外屏碎裂的修复建议
印象笔记如何设提醒任务防漏执行_印象笔记设提醒任务防漏执行【任务提醒】
在J*a中如何使用BigDecimal进行高精度计算_BigDecimal类应用指南
免费抖音短视频入口_抖音网页版短视频免费通道
Angular中父组件异步更新子组件复选框状态的实践指南
使用 Pandas 高效处理 .dat 文件:字符清理与数据计算
React Router 嵌套组件中 URL 重定向问题的解决方案
在J*a中如何在J*a中使用异常机制记录错误日志_异常日志实践经验
我的世界mc.js免费游戏直接能玩 我的世界mc.js小游戏免费秒玩入口
html网页设计源代码怎么运行_运行html网页设计源代码步骤【指南】
Steam官网入口直达 Steam注册及登录步骤
C++如何比较两个字符串_C++ string compare函数与操作符对比
凉拌黄瓜怎么拌更入味 凉拌黄瓜简单家常做法
黑猫投诉统一入口官网 消费者权益保护投诉平台
在J*a中如何开发简易仓库管理与库存统计_仓库管理库存统计项目实战解析
天眼查怎么看公司融资情况 天眼查企业融资历史查询步骤【攻略】
FullCalendar 自定义按钮样式定制指南
解决Rails应用中内容错位与Turbo警告:meta标签误用导致富文本渲染异常
Python异步编程实践:使用Binance API构建实时交易数据流
J*aScript中正确使用querySelectorAll与复杂CSS选择器
CSS布局:解决全屏元素100%尺寸与外边距导致的页面溢出问题
实现全屏滚动与导航点:专业教程
C++20的source_location是什么_C++在编译期获取源码位置信息用于日志和断言
抖音网页版怎么|直播|_抖音网页版开播操作指南
Golang如何优化内存分配与垃圾回收_Golang内存管理与GC优化实践
Safari浏览器输入栏卡顿如何解决 Safari搜索建议与缓存清理
Android Studio计算器C键功能异常排查与修复教程
Angular Material 垂直步进器:实现底部到顶部排序的教程
J*aScript数组对象转换:按指定键分组与值收集
J*a里如何实现订单支付与库存同步功能_支付库存同步项目开发方法说明
解决Flask中Quill编辑器内容提交失败及TypeError的指南
消息称三星明年 2 月正式发布 HBM4,与 SK 海力士同台竞技
J*aScript map 方法中处理循环元素为空数组的策略
b站怎么看视频的弹幕数量_b站弹幕数量查看方法
Yandex搜索引擎官网入口_俄罗斯Yandex免登录一键直达
Selenium Python中处理点击后新窗口加载冻结问题的策略与实践
Go RPC HTTP服务正确实现与常见陷阱解析
iCloud登录入口网页版 苹果iCloud官网登录
在J*a中如何开发在线活动报名与管理系统_活动报名管理项目实战解析
Angular响应式表单:实现提交后表单及按钮的禁用与只读化
Django表单提交验证失败后保持字段值不刷新
Golang如何通过reflect操作map_Golang reflect map操作与遍历技巧
Lar*el表单中优雅地处理“返回”按钮以规避验证:最佳实践指南


2025-10-28
浏览次数:次
返回列表
d)
// --------------------------------------------------
// critical section 1 (CS1)
// 这里放置f1的临界区代码
time.Sleep(100 * time.Millisecond) // 模拟临界区工作
// end critical section 1
// --------------------------------------------------
next <- true // 完成CS1后,将令牌传递给下一个协程
}
}
// f2 协程负责执行临界区2
func f2(id int, do chan bool, next chan bool) {
for { // 持续循环以演示交替执行
<-do // 等待接收令牌,表示轮到f2执行
fmt.Printf("Goroutine %d: Executing Critical Section 2\n", id)
// --------------------------------------------------
// critical section 2 (CS2)
// 这里放置f2的临界区代码
time.Sleep(100 * time.Millisecond) // 模拟临界区工作
// end critical section 2
// --------------------------------------------------
next <- true // 完成CS2后,将令牌传递给下一个协程
}
}
func main() {
// 创建两个缓冲大小为1的通道,用于传递令牌
cf1 := make(chan bool, 1) // f1的接收通道
cf2 := make(chan bool, 1) // f2的接收通道
// 初始令牌:向cf1发送一个令牌,让f1首先开始执行
cf1 <- true
// 启动两个协程
go f1(1, cf1, cf2) // f1接收cf1的令牌,完成后将令牌发送给cf2
go f2(2, cf2, cf1) // f2接收cf2的令牌,完成后将令牌发送给cf1
// 保持主协程运行,否则程序会立即退出,无法观察到子协程的交替执行
select {} // 阻塞主协程,直到程序被手动终止
}