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

线程之并发限制

文章目录

  • 为什么需要创建并发限制?
    • 不用并发限制会怎样?
    • 怎么设置并发限制
    • 如果任务是I/O密集型,可以适当放宽并发数吗?
    • 线程是有限资源,那4核8线程的CPU,线程数就是8吗?
  • CPU的“线程”与操作系统的“线程”
  • 总结

        protected async Task OperateModbusObjectToEnabledClientsAsync(Func<ModbusObjectDemo, Task> func, string operationName = ""){try{if (func == null)throw new ArgumentNullException(nameof(func));// 获取所有已启用写操作的客户端var enabledClients = _clientProvider.GetEnabledClients();if (!enabledClients.Any()){StatusMessage = "No enabled clients found!";logger.Debug(StatusMessage);return;}logger.Trace("Action:客户端数量:{0},写操作开始:{1}", enabledClients.Count(), operationName);// 使用一种轻量级的信号量来创建并发限制,用于限制同时运行的任务数量,避免同时处理太多客户端。using var semaphore = new SemaphoreSlim(Environment.ProcessorCount);var tasks = new List<Task<ClientOperationResult>>();foreach (var client in enabledClients){tasks.Add(ProcessClientAsync(client, func, semaphore));}// 等待所有任务完成,设置超时时间var timeoutTask = Task.Delay(TimeSpan.FromSeconds(20)); // 设置20秒超时// 等待所有任务和超时任务完成。// 使用Action版本(无返回值),这里的WhenAll只会等待Task.Run完成,而不会等待action中的异步操作。// 使用Func<Task>的版本,这里的WhenAll会等待所有异步操作完成。var completedTask = await Task.WhenAny(Task.WhenAll(tasks), timeoutTask);if (completedTask == timeoutTask)//如果是超时任务完成则报错{logger.Error($"Operation {operationName} timed out after 20 seconds");StatusMessage = $"Operation {operationName} timed out";return;}var results = await Task.WhenAll(tasks);logger.Trace("Action:客户端数量:{0},写操作结束:{1}", enabledClients.Count(), operationName);ProcessResults(results, operationName);}catch (Exception ex){StatusMessage = $"Error in {operationName}: {ex.Message}";logger.Debug(StatusMessage);}}private async Task<ClientOperationResult> ProcessClientAsync(IModbusTcpClient client, Func<ModbusObjectDemo, Task> func, SemaphoreSlim semaphore){try{// 等待获取信号量await semaphore.WaitAsync();logger.Trace("信号量数量:{0}", semaphore.CurrentCount);if (!client.IsConnected){return new ClientOperationResult{Client = client,Success = false,Error = "Not connected"};}// 直接等待异步操作,不需要 Task.Runawait client.TryGetModbusObjectDemoAsync(func);return new ClientOperationResult{Client = client,Success = true,Error = string.Empty};}catch (Exception ex){logger.Error($"Error processing client {client.Name}: {ex.Message}");return new ClientOperationResult{Client = client,Success = false,Error = ex.Message};}finally{semaphore.Release();}}

为什么需要创建并发限制?

  • 原因一:线程资源是有限的

    • 线程不是“无限”资源。每创建一个线程,都会占用一定的内存和CPU调度资源。
    • 如果有很多客户端(比如几十、几百个),每个都开一个线程去处理,系统很快就会因为线程数过多而变慢甚至崩溃(线程上下文切换开销大,内存消耗大)。
  • 原因二:防止“线程风暴”

    • 如果不加限制,所有任务会同时并发执行,可能导致CPU、内存、网络等资源被瞬间耗尽,影响整个系统的稳定性。
  • 原因三:提升整体吞吐量和响应性

    • 合理的并发数可以让CPU和I/O资源得到充分利用,同时又不会让系统过载。
    • 这样可以保证每个任务都能在合理的时间内完成,系统整体更平稳。

不用并发限制会怎样?

  • 可能会出现线程爆炸,导致系统变慢甚至崩溃。
  • 任务之间会频繁切换,CPU大部分时间都在做“线程切换”而不是“真正的工作”。
  • 系统响应变慢,甚至出现死锁、内存溢出等问题。

怎么设置并发限制

  • 如示例代码中使用轻量级的信号量(SemaphoreSlim),用于限制同时运行的任务数量。可以把它理解为“并发闸门”,只有拿到“通行证”的任务才能执行,其他任务要等前面的任务释放“通行证”后才能继续。

Environment.ProcessorCount 的含义

  • Environment.ProcessorCount 返回当前机器的逻辑处理器(CPU核心)数量。
  • 例如,4核8线程的CPU,这个值就是8。也就是说通过信号量(SemaphoreSlim)限制同时只允许8个线程同时处理。

为什么用它

  • 这是一个经验值,通常来说,CPU密集型任务的并发数设置为核心数可以获得最优性能。
  • 即使是I/O密集型任务,设置为核心数也能保证不会有太多任务同时抢占CPU,避免线程切换开销过大。
  • 这样做的好处是:既能利用多核CPU的并发能力,又不会让系统资源被过度消耗。
  • 举例说明
    • 假设你有100个客户端,如果不加限制,100个任务会同时执行,系统压力很大。
    • 如果用 SemaphoreSlim(8),每次只允许8个任务并发执行,其他的排队,等前面的执行完再继续。

如果任务是I/O密集型,可以适当放宽并发数吗?

  • 可以!对于I/O密集型任务(比如网络、磁盘操作),可以适当把并发数调大一些(比如2~4倍核心数),但也要根据实际测试和服务器能力来调整。
  • 但绝不能无限制地放大,否则还是会有资源耗尽的风险。

线程是有限资源,那4核8线程的CPU,线程数就是8吗?

  • 我们可以创建远远多于8个线程(比如100、1000甚至更多),但这样做会带来:
    • 内存消耗(每个线程有自己的栈空间,通常1~2MB)
    • 线程切换的CPU开销(上下文切换)
    • 系统调度压力
  • 实际“有限资源”取决于:
    • 你的机器内存有多少
    • 你的操作系统允许的最大线程数
    • 你的应用场景(CPU密集型还是I/O密集型)
  • 一般来说:
    • 线程数在几十到几百是没问题的(取决于内存和场景)
    • 但同一时刻能被CPU真正执行的线程数,就是逻辑核心数(比如8)
  • 这就要引入下一个话题:CPU的“线程”与操作系统的“线程”

CPU的“线程”与操作系统的“线程”

  • CPU的线程(比如4核8线程)指的是CPU能同时并行执行的最大任务数,也就是“并发执行单元”。
    4核8线程,代表有4个物理核心,每个核心支持超线程(Hyper-Threading),所以一共8个“逻辑核心”。
    这意味着最多有8个任务可以真正同时被CPU执行。

  • 操作系统的线程(比如C#里的Thread/Task)是软件层面的调度单元。
    操作系统可以创建成百上千个线程,但同一时刻只有最多8个线程能被CPU真正执行(在4核8线程的机器上)。
    其他线程会被挂起、等待,操作系统会不断切换它们上CPU。

总结

  • 4核8线程的CPU,不是说你只能创建8个线程,而是同一时刻最多8个线程能被CPU并行执行。
  • 你可以创建更多线程,但会带来资源消耗和调度开销。
  • 线程是有限资源,但这个“有限”不是8,而是受内存、操作系统和应用场景共同决定的。
  • 用 Environment.ProcessorCount 作为并发限制,是为了让你的程序既高效又不会让系统过载(频繁在线程上下文中切换)。
http://www.lryc.cn/news/572253.html

相关文章:

  • C语言项目实践——贪吃蛇
  • Python Redis 简介
  • Day05_数据结构总结Z(手写)
  • 设计模式精讲 Day 7:桥接模式(Bridge Pattern)
  • 68、数据访问-crud实验-删除用户完成
  • 优化TCP/IP协议栈与网络层
  • 十年年化50%+的策略如何进化?兼容机器学习流程的量化策略开发,附python代码
  • WOOT BD活动背后的策略与操作
  • openKylin适配RISC-V高性能服务器芯片,携手睿思芯科共拓智算新蓝海
  • Linux head 命令
  • 软件项目管理(第4版)部分课后题答案
  • 腾讯云TCCP认证考试报名 - TDSQL数据库交付运维高级工程师(MySQL版)
  • 【设计模式】用观察者模式对比事件订阅(相机举例)
  • 智能混合检索DeepSearch
  • 《二叉搜索树》
  • Git版本控制详细资料
  • Postman 的 Jenkins 管理 - 自动构建
  • ABP VNext + MongoDB 数据存储:多模型支持与 NoSQL 扩展
  • 【深度学习】生成对抗网络(GANs)深度解析:从理论到实践的革命性生成模型
  • 理想树获沙利文认证,赢得中学教辅图书市场认可
  • java Class类反射getDeclaredMethod() 和 getMethod()的区别
  • Linux中的阻塞信号与信号原理
  • Linux 并发编程:从线程池到单例模式的深度实践
  • 用 STM32 HAL/LL + Arduino 混合编程
  • 硬件-DAY04(ds18b20、ARM内核)
  • Python打卡:Day31
  • 矩阵置零C++
  • Linux:信号和线程
  • 如何在 Pop!_OS 或 Ubuntu Linux 上安装 Dash to Dock
  • 设备巡检系统小程序ThinkPHP+UniApp