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

C# 线程同步(一)同步概念介绍

目录

1.阻塞(Blocking)

2.阻塞 VS 轮询

3.线程状态


        到目前为止,我们已经阐述了如何在线程上启动任务、配置线程以及实现双向数据传递。同时,我们也说明了局部变量是线程私有的,而引用可以通过共享字段在线程间传递以实现通信。

下一步的关键是同步机制:通过协调线程行为来获得可预测的结果。当多个线程访问同一数据时,同步显得尤为重要——这个领域看似简单却暗藏风险。

同步构造可分为四大类别:

  1. 简单阻塞方法
    这类方法通过等待其他线程结束或计时完成来实现同步。例如:SleepJoin 和 Task.Wait 都属于简单阻塞方法。

  2. 锁定构造
    用于限制同时执行某段代码或操作的线程数量。最常见的独占锁(如 lock/Monitor.Enter/Monitor.ExitMutex 和 SpinLock)仅允许一个线程进入,确保竞争线程访问共享数据时互不干扰。非独占锁包括信号量(Semaphore/SemaphoreSlim)和读写锁。

  3. 信号构造
    允许线程暂停运行直至接收到其他线程的通知,从而避免低效的轮询。常用信号机制有两种:事件等待句柄(event wait handles)和 Monitor 的 Wait/Pulse 方法。.NET Framework 4.0 新增了 CountdownEvent 和 Barrier 类。

  4. 非阻塞同步构造
    通过调用处理器原语来保护共享字段的访问。CLR 和 C# 提供的非阻塞构造包括:Thread.MemoryBarrierThread.VolatileReadThread.VolatileWritevolatile 关键字以及 Interlocked 类。

除最后一类外,阻塞机制在其他类别中均占核心地位。下面我们将简要探讨这一概念。

1.阻塞(Blocking)

        当线程因某些原因暂停执行时(例如通过 Sleep 进入休眠,或通过 Join/EndInvoke 等待其他线程结束),该线程即被视为阻塞状态。阻塞线程会立即释放其处理器时间片,此后在阻塞条件满足前不再消耗任何处理器资源。可通过线程的 ThreadState 属性检测阻塞状态:

bool blocked = (someThread.ThreadState & ThreadState.WaitSleepJoin) != 0;
(由于线程状态可能在检测与后续操作之间发生变化,此代码仅适用于诊断场景。)

当线程阻塞或解除阻塞时,操作系统会执行上下文切换。这将产生几微秒的开销。

线程会通过以下四种方式解除阻塞(当然,按电脑电源键不算!):

  1. 阻塞条件得到满足

  2. 操作超时(如果指定了超时时间)

  3. 通过Thread.Interrupt被中断

  4. 通过Thread.Abort被中止

需要注意的是,如果线程是通过(已弃用的)Suspend方法暂停执行的,则不会被判定为处于阻塞状态。

2.阻塞 VS 轮询

当线程需要暂停直到特定条件满足时,通常有两种实现方式:

  1. 阻塞等待(高效方式)
    通过信号和锁机制实现,线程会进入阻塞状态直到条件满足,此时操作系统会调度其他线程执行。

  2. 轮询等待(简单但低效)
    线程通过循环检测条件来实现等待,例如:

while (!proceed);  // 忙等待

while (DateTime.Now < nextStartTime); // 时间等待

性能考量:

  • 纯轮询会完全占用CPU资源,因为CLR和操作系统会认为线程正在进行重要计算

  • 这种方式的CPU利用率是100%,极其浪费系统资源

改进方案:
可以采用混合阻塞的方式:

while (!proceed) Thread.Sleep(10);  // 每次检查后休眠10ms

虽然不够优雅,但相比纯轮询能显著降低CPU占用率。不过需要注意共享变量(如proceed标志)的并发访问问题,正确的锁和信号量使用可以避免这些问题。

适用场景:
当预期条件能在极短时间内(如几微秒)满足时,短暂轮询可能更高效,因为它避免了上下文切换的开销和延迟。.NET框架为此提供了专门的工具类和方法,这些内容将在并行编程章节详细介绍。

这里小节一下:

  • 阻塞等待:适合大多数情况,资源利用率高

  • 纯轮询:简单但资源浪费严重

  • 混合模式:折中方案,需要处理并发问题

  • 微秒级等待:特殊场景下短暂轮询可能更优

3.线程状态

        您可以通过ThreadState属性查询线程的执行状态。该属性返回一个ThreadState类型的标志枚举,它以位运算方式组合了三个"层次"的数据。不过,大多数枚举值都是冗余的、未使用的或已废弃的。下图展示了其中一个"层次":

以下代码可将ThreadState精简为四个最常用的状态值:未启动(Unstarted)、运行中(Running)、等待休眠或加入(WaitSleepJoin)和已停止(Stopped):

public static ThreadState SimpleThreadState(ThreadState ts)
{return ts & (ThreadState.Unstarted |ThreadState.Running |ThreadState.WaitSleepJoin |ThreadState.Stopped);
}

ThreadState属性适用于诊断目的,但不适合用于同步控制,因为在检测线程状态和基于该状态执行操作之间,线程状态可能会发生变化。


本节完,下一节将开始介绍同步工具

http://www.lryc.cn/news/579326.html

相关文章:

  • 网络安全的未来趋势与挑战
  • 好用的自带AI功能的国产IDE
  • Java-63 深入浅出 分布式服务 网络通信 RPC 与 RMI 详解
  • Spring 为何需要三级缓存解决循环依赖,而不是二级缓存
  • 【网络安全】Webshell命令执行失败解决思路
  • 【第十一篇】SpringBoot缓存技术
  • Javaweb - 10.1 Servlet
  • C盘空间的“元凶”——虚拟内存的神秘面纱
  • css ::before学习笔记
  • 专业AI工具导航与人工智能学习平台AIbase.cn 连接现在与AI未来的智能桥梁
  • YOLO基础算法入门之YOLOv8中的C2f(C2-Faster)高效特征提取结构
  • STC8G 8051内核单片机开发 (中断)
  • 算法学习笔记:4.KMP 算法——从原理到实战,涵盖 LeetCode 与考研 408 例题
  • 家政维修小程序源码php方案解析
  • FASTAPI+VUE3平价商贸管理系统
  • 实际开发如何快速定位和解决死锁?
  • thinkphp中间件
  • 协同过滤推荐算法
  • 动态规划-P1216 [IOI 1994] 数字三角形 Number Triangles
  • RAG实战指南 Day 4:LlamaIndex框架实战指南
  • AutoMedPrompt的技术,自动优化提示词
  • 基于 govaluate 的监控系统中,如何设计灵活可扩展的自定义表达式函数体系
  • 【学习线路】机器学习线路概述与内容关键点说明
  • 解决 Spring Boot 对 Elasticsearch 字段没有小驼峰映射的问题
  • STC8G 8051内核单片机开发(GPIO)
  • “Payload document size is larger than maximum of 16793600.“问题解决(MongoDB)
  • C++ 网络编程(14) asio多线程模型IOThreadPool
  • PyTorch 安装使用教程
  • EXCEL小妙招——判断A列和B列是否相等
  • AI时代SEO关键词策略