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

令牌环式同步:乒乓球对练的四种实现方式

在并发编程中,"轮流执行"或"交替执行"是一种常见的同步模式。更正式的说法包括令牌环式同步(Token-Ring Synchronization)互斥交替(Mutual Exclusion Alternation)。本文将通过乒乓球对练的经典示例,展示四种不同的实现方式,并分析它们的优缺点。

问题描述

实现两个线程交替打印"Ping"和"Pong",共打印100次,形成"Ping Pong Ping Pong..."的交替序列。

实现方式

1. ManualResetEvent 实现

internal class ManualResetTestCode
{public static void Print(){var ping = new ManualResetEvent(true);var pong = new ManualResetEvent(false);// 线程 ATask.Run(() =>{for (int i = 0; i < 100; i++){ping.WaitOne();    // ManualResetEvent的WaitOne不会自动重置,需要手动调用ResetConsole.WriteLine("Ping");pong.Set();ping.Reset();}});// 线程 BTask.Run(() =>{for (int i = 0; i < 100; i++){pong.WaitOne();Console.WriteLine("Pong");ping.Set();pong.Reset();}});}
}

工作原理

  • 使用两个ManualResetEvent对象,初始状态分别为true(ping可以立即执行)和false(pong需要等待)
  • 每个线程执行前调用WaitOne()等待信号
  • 执行完成后,通过Set()通知对方线程,同时通过Reset()重置自己的信号

优缺点

  • ✅ 简单直观,易于理解
  • ✅ 可以手动控制信号状态
  • ❌ 需要显式调用Reset(),容易遗漏导致逻辑错误
  • ❌ 性能略差,因为每次都需要手动重置

2. AutoResetEvent 实现

internal class AutoResetControlTestCode
{public static void Print(){var ping = new AutoResetEvent(true);var pong = new AutoResetEvent(false);// 线程 ATask.Run(() =>{for (int i = 0; i < 100; i++){ping.WaitOne();  // WaitOne() 只在事件处于非终止状态时阻塞,并自动重置为falseConsole.WriteLine("Ping");pong.Set();}});// 线程 BTask.Run(() =>{for (int i = 0; i < 100; i++){pong.WaitOne();Console.WriteLine("Pong");ping.Set();}});}
}

工作原理

  • 使用两个AutoResetEvent对象,初始状态与ManualResetEvent版本相同
  • WaitOne()方法会自动将事件状态重置为false,无需手动调用Reset()
  • 线程执行完成后,通过Set()通知对方线程

优缺点

  • ✅ 自动重置,减少了手动操作,降低了出错概率
  • ✅ 代码更简洁,逻辑更清晰
  • ✅ 性能比ManualResetEvent稍好
  • ❌ 无法手动控制信号状态,灵活性稍差

3. TaskCompletionSource 实现

public class TaskCompletionSourceTestCode
{public static async Task PrintAsync(){var pingTcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);var pongTcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);// 先让 ping 侧可以立即发球pingTcs.SetResult(true);// 线程 Avar a = Task.Run(async () =>{for (int i = 0; i < 100; i++){await pingTcs.Task;     // 等"可以发 Ping"的信号Console.WriteLine("Ping");pingTcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);pongTcs.SetResult(true); // 通知对方可以发 Pong}});// 线程 Bvar b = Task.Run(async () =>{for (int i = 0; i < 100; i++){await pongTcs.Task;     // 等"可以发 Pong"的信号Console.WriteLine("Pong");pongTcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);pingTcs.SetResult(true); // 通知对方可以发 Ping}});await Task.WhenAll(a, b);}
}

工作原理

  • 使用TaskCompletionSource创建可等待的任务
  • 初始时,将ping的任务标记为完成,允许ping立即执行
  • 每个线程执行前等待对应的Task完成
  • 执行完成后,创建新的TaskCompletionSource对象,并将对方的任务标记为完成

优缺点

  • ✅ 支持异步编程模型,使用await语法更现代
  • ✅ 避免了线程阻塞,提高了资源利用率
  • ✅ 可以传递实际数据,不仅仅是信号
  • ❌ 每次迭代都需要创建新的TaskCompletionSource对象,有一定的内存开销
  • ❌ 代码相对复杂,需要理解异步编程模型

4. Channel 实现

public class ChannelTestCode
{public static async Task PrintChannelAsync(){var pingCh = Channel.CreateUnbounded<bool>();var pongCh = Channel.CreateUnbounded<bool>();// 先让 ping 侧可以立即发球await pingCh.Writer.WriteAsync(true);var a = Task.Run(async () =>{for (int i = 0; i < 100; i++){await pingCh.Reader.ReadAsync(); // 等"可以发 Ping"Console.WriteLine("Ping");await pongCh.Writer.WriteAsync(true); // 通知对方}});var b = Task.Run(async () =>{for (int i = 0; i < 100; i++){await pongCh.Reader.ReadAsync(); // 等"可以发 Pong"Console.WriteLine("Pong");await pingCh.Writer.WriteAsync(true); // 通知对方}});await Task.WhenAll(a, b);}
}

工作原理

  • 使用Channel创建无界通道
  • 初始时,向ping通道写入数据,允许ping立即执行
  • 每个线程从自己的通道读取数据(等待信号)
  • 执行完成后,向对方通道写入数据(发送信号)

优缺点

  • ✅ 现代异步编程模型,设计更优雅
  • ✅ 高性能,适合高并发场景
  • ✅ 内置了异步支持,无需手动创建任务
  • ✅ 可以轻松扩展为传递复杂数据
  • ❌ 需要.NET Core 3.0+ 或 .NET 5+ 支持
  • ❌ 对于简单场景,可能显得过于复杂

四种实现方式比较

实现方式 技术类型 异步支持 自动重置 性能 代码复杂度 适用场景
ManualResetEvent 传统同步 中等 简单 简单同步场景,需要手动控制信号
AutoResetEvent 传统同步 中等 简单 简单同步场景,自动控制信号
TaskCompletionSource 异步 较高 中等 异步场景,需要灵活控制任务状态
Channel 现代异步 中等 高并发异步场景,需要传递数据

结论

四种实现方式各有优缺点,选择哪种取决于具体场景:

  1. 传统同步场景:如果项目使用的是旧版本.NET Framework,或者需要简单的同步控制,AutoResetEvent是不错的选择。

  2. 异步编程场景:如果项目使用的是.NET Core 3.0+ 或 .NET 5+,Channel是更现代、更高效的选择。

  3. 灵活控制需求:如果需要更灵活地控制任务状态,或者需要传递复杂数据,TaskCompletionSource提供了更多的灵活性。

  4. 简单场景:对于简单的交替执行需求,任何一种实现方式都可以胜任,选择最容易理解和维护的即可。

令牌环式同步是并发编程中的常见模式,通过不同的实现方式,我们可以看到并发编程的演变过程:从传统的同步原语到现代的异步编程模型。了解这些实现方式,有助于我们在实际项目中选择合适的同步机制,编写高效、可靠的并发代码。

扩展思考

  1. 如果需要三个或更多线程交替执行,如何修改这些实现?
  2. 如果线程执行时间不一致,这些实现会有什么问题?如何解决?
  3. 在高并发场景下,哪种实现方式的性能最好?
  4. 如何测试这些实现的正确性和性能?

通过思考这些问题,我们可以更深入地理解并发编程中的同步机制,以及各种实现方式的适用场景。

http://www.gsyq.cn/news/69243.html

相关文章:

  • 2025年11月助听器十大品牌推荐 自然之声以专业康复服务位
  • 歌声转换SVC主流方法原理剖析3 — So-VITS-SVC
  • 2025全国助听器厂家推荐榜:专业验配+全程康复成听损家
  • 超市环境智能监控方案:96.8% 识别精度,异味投诉归零
  • 小程序定制开发实战:需求拆解、UI 设计与个性化效果落地流程
  • 配电自动化(FTU、DTU、TTU)芯片国产化选择
  • 实验四 组合与继承
  • 编程题库 No.16 加班薪水UP
  • 百练PKU / 2024计算机学院推免上机考试考情分析与备考建议
  • 详细介绍:css学习盒模型:
  • python题库 No.17 大运预选
  • 基于CNN卷积神经网络和GEI步态能量提取的视频人物步态识别算法matlab仿真
  • 12/2
  • 12.13任务
  • 去颈纹用哪种颈霜效果好?2025临床数据揭秘真正有效的抗颈纹产品
  • 别再只懂二分类!逻辑回归+Softmax多分类实战,保姆级教程奉上 - 详解
  • 手把手带你通关Webug第一关:看懂SQL注入是怎么“玩”的
  • 技术总监亲述:工作授权不是甩锅,掌握这8步让团队战斗力提升300%
  • 关于Proteus在编译时提示Failed to set firmware property.的问题
  • 成群结队 - 冲刺总结
  • 从 Pandas 转向 Polars:新手常见的10 个问题与优化建议
  • 二进制兼容
  • 成群结队--冲刺计划
  • 第五天敏捷冲刺
  • Java进阶网络编程,UDP,TCP通信
  • IDEA标签窗口好行显示 类注释和方法注释
  • LabVIEW用直线边缘检测实现液位测量 - 教程
  • HEK293细胞:为什么它是重组蛋白表达的黄金标准?
  • 树莓派Docker部署AdGuard Home
  • 人工智能发展史简述