关于并发控制的简单整理

本文通过餐厅厨房的比喻介绍了并发控制的概念,探讨了硬件方式如中断禁用和机器指令,软件方法如Dekker's Algorithm、Peterson's Algorithm和Lamport's Bakery Algorithm,以及基于信号量和管程的高级抽象数据结构在并发控制中的应用。并发控制旨在解决资源安全、分配和程序设计错误等问题,通过不同机制确保进程的正确协调和资源保护。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在开始之前,先引入一个情景:

环境是一个餐厅的厨房,人物当然是一群厨师。

每当饭点,厨房的厨师需要做菜。厨师中又分有主厨,副厨,一些打下手的,还有学徒。显然厨师之间需要配合协助,同时对于多个打下手的人之间又会出现一些竞争(比如:刀具的使用,食材的选择,餐盘的使用等等),还有一些与做菜无关的人员,对于学徒,也许只是让他们在旁边拖拖地,洗洗盘,并没有严格的融入到做菜这个行为中去。

如果我们将这个情景抽象为一个操作系统中的情景,那么对应应该是这样的,各种刀具,餐具,食材等等厨房的用具都是硬件资源,厨师(除了主厨)的行为可以代表为一个进程(或线程),厨师之间的配合规则是一个同步的机制,厨师之间对于某些资源的争用,则是该片主要内容互斥问题。

引入厨房和厨师的介绍,希望可以让大家对并发控制所涉及的处理对象有一个直观的印象,对于并发控制的介绍,先从基础开始(对于基础已知,可跳过此处)。

和并发相关的关键术语
   术语                                                                                 说明
原子操作 一个或多个指令的序列,对外不可分;即没有其他进程可以看到其中间状态或中断
临界区 是一段代码,在这段代码中进程将访问共享资源,当另外一个进程在这段代码中运行时,这个进程
不能在这段代码中执行
死锁 两个或两个以上的进程因其中的每个进程都在等待其他进程昨晚某些事情而不能继续执行的情形
互斥 当一个进程在临界区访问共享资源时,其他进程不能进入该临界区访问任何共享资源的情形
竞争条件 多个线程或者进程在读写一个共享数据是,结果依赖于他们执行的相对时间的情形
饥饿 指一个可运行的进程尽管能继续执行,但被调度器无限期的忽视,而不能被调度执行的情况










进程的交互
交互程度 关系 进程之间的影响 潜在控制问题
进程之间不感知 竞争 *一个进程的结果与其他进程的活动无关
*进程的执行时间可能会受影响
*互斥
*死锁(可复用的资源)
*饥饿
进程之间间接感知
(如共享对象)
共享合作 *一个进程的结构可能依赖于从其他进程获得的消息
*进程的执行可能会受到影响
*互斥
*死锁(可复用的资源)
*饥饿
*数据一致性
进程直接感知
(存在通信原语)
通信合作 *一个进程的结果可能依赖于从其他进程获得的信息
*进程的即使可能会受到影响
*死锁(可消费的资源)
*饥饿









(注:上表,均参考操作系统精髓与设计原理第6版中文译本,陈向群、陈渝,机械工业出版社)


由于上述进程交互之间的关系,同时多道程序设计系统中交替和重叠的执行模式下,进程的相对执行速度不可预测,所以会带来一下困难。

1,全局资源的共享并不安全

2、很难做好资源的分配

3、程序设计错误很难调试。

并发控制就需要处理好上述的困难&

<think>好的,用户之前问过如何指定C# Task的最大线程数,现在又问了如何控制并发避免CPU使用过高。我需要先理解这两个问题之间的关系。用户可能是在使用多线程或异步任务时遇到了CPU占用过高的问题,想知道如何有效控制并发,防止资源耗尽。 首先,用户可能是在处理大量并行任务时,系统创建了过多的线程,导致CPU负载过大。这时候需要限制并发数量,或者调整任务调度方式。之前的回答已经提到了一些方法,比如使用ParallelOptions、SemaphoreSlim、自定义TaskScheduler等。现在需要结合避免CPU过高的场景,给出更具体的建议。 接下来,我要考虑用户的实际应用场景。可能是数据处理、网络请求、或是计算密集型任务。不同的场景可能需要不同的策略。例如,计算密集型任务需要限制并行度,避免线程过多导致频繁的上下文切换;而IO密集型任务可能更适合异步操作,减少线程阻塞。 然后,用户可能的真实需求是优化程序性能,防止因高并发导致的系统资源耗尽,尤其是CPU。他们可能已经尝试过简单的多线程,但遇到了性能问题,需要更精细的控制方法。 需要回顾之前的回答,确保不重复同样的内容,同时补充新的建议。例如,之前提到过SemaphoreSlim,这里可以进一步强调其在控制并发中的作用;另外,可能需要引入其他机制,如取消长时间任务、调整线程池设置、使用异步编程模型等。 还要注意用户可能的误区,比如误用Thread.Sleep,或者没有正确区分IO密集和计算密集型任务的处理方式。需要指出这些常见错误,并提供替代方案。 最后,整理建议,分点说明,确保结构清晰,并提供代码示例帮助用户理解。同时,比较不同方法的适用场景,帮助用户根据具体情况选择最佳方案。</think> C# 中,控制并发以避免 CPU 使用过高需要合理管理并行任务的数量和资源分配。以下是常见场景和实现方法: --- ### **1. 区分 I/O 密集型与 CPU 密集型任务** - **I/O 密集型**(如网络请求、文件读写): 使用异步编程(`async/await`)避免阻塞线程,让线程池线程释放给其他任务。 ```csharp async Task DownloadDataAsync() { await httpClient.GetAsync("https://example.com"); // 异步操作不会长时间占用 CPU } ``` - **CPU 密集型**(如复杂计算): 需**显式限制并行度**,避免过度占用 CPU 核心。 -- ### **2. 限制并行任务数量** #### **方法 1:使用 `SemaphoreSlim`(推荐)** 通过信号量控制同时运行的并发任务数: ```csharp var semaphore = new SemaphoreSlim(maxConcurrency: Environment.ProcessorCount); // 按 CPU 核心数限制 List<Task> tasks = new List<Task>(); for (int i = 0; i < 100; i++) { tasks.Add(Task.Run(async () => { await semaphore.WaitAsync(); try { // CPU 密集型任务(例如复杂计算) Calculate(i); } finally { semaphore.Release(); } })); } await Task.WhenAll(tasks); ``` #### **方法 2:`Parallel.For` 控制并行度** 针对循环类任务,直接限制最大并行度: ```csharp ParallelOptions options = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount // 根据 CPU 核心数调整 }; Parallel.For(0, 100, options, i => { Calculate(i); }); ``` --- ### **3. 避免阻塞线程池** - **不要滥用 `Task.Run`**: 将 I/O 操作直接标记为 `async`,无需包装到 `Task.Run` 中。 ```csharp // ✅ 正确写法(I/O 操作) async Task LoadDataAsync() { var data = await File.ReadAllTextAsync("data.txt"); } // ❌ 错误写法(无意义占用线程池) async Task LoadDataAsync() { await Task.Run(() => File.ReadAllText("data.txt")); // 同步方法阻塞线程 } ``` --- ### **4. 调整线程池设置(谨慎使用)** 全局限制线程池最大线程数,避免过多线程竞争 CPU: ```csharp int workerThreads = Environment.ProcessorCount * 2; // 合理设置上限 int completionPortThreads = Environment.ProcessorCount * 2; ThreadPool.SetMaxThreads(workerThreads, completionPortThreads); ``` > **警告**:全局修改线程池会影响所有依赖线程池的代码(如 ASP.NET 请求处理)。 -- ### **5. 使用 `CancellationToken` 终止长时间任务** 防止失控的任务持续占用 CPU: ```csharp var cts = new CancellationTokenSource(); Task.Run(() => { while (true) { if (cts.Token.IsCancellationRequested) { break; // 及时退出循环 } // 执行计算 } }, cts.Token); // 超时后取消任务 cts.CancelAfter(TimeSpan.FromSeconds(10)); ``` -- ### **6. 监控 CPU 使用率** 通过性能计数器动态调整并发策略: ```csharp using (var pc = new PerformanceCounter("Processor", "% Processor Time", "_Total")) { if (pc.NextValue() > 80) // CPU 使用率超过 80% { // 动态减少并发任务数量 maxConcurrency = Math.Max(1, maxConcurrency - 1); } } ``` --- ### **关键总结** | 场景 | 解决方案 | 核心目标 | |--|--------------------------| | CPU 密集型任务 | `SemaphoreSlim` / `Parallel.For` | 限制并行度,匹配 CPU 核心 | | I/O 密集型任务 | `async/await` | 释放线程,减少阻塞 | | 全局资源紧张 | 调整线程池或动态监控 | 避免过度竞争 CPU | | 长时间运行的任务 | `CancellationToken` | 防止任务失控 | --- ### **常见误区** - **过度并行化**:并非线程越多越好,过多的线程会导致上下文切换开销。 - **滥用 `Task.Run`**:将同步代码包装为 `Task.Run` 会浪费线程池资源。 - **忽略异步方法**:未正确使用 `async/await` 会导致线程阻塞。 根据任务类型选择合适的并发策略,可以有效平衡性能和 CPU 利用率!
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值