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

C# 多线程编程核心要点:不只是Thread和lock

聊到C#多线程,很多人第一反应就是Threadlock。没错,它们是基石,但如果你只停留在它们,那就像只会用菜刀切菜,永远做不出满汉全席。现代C#多线程的核心思想是 “高效地利用计算资源,并安全地处理并发”。下面我跟你捋几个最核心的点,保证接地气。


1. 为什么要用多线程?—— “别让CPU看戏”

想象一下,你的程序需要从网上下载10个文件。如果用单线程,它就是傻乎乎地一个一个下,期间CPU大部分时间都在那“空转”(等待网络响应)。多线程的核心目的就是充分利用多核CPU的计算能力,把这种“等待”的时间利用起来,让其他线程去干活,或者把一个大任务拆成多个小任务同时处理,极大提升程序性能和响应速度。

  • CPU密集型:计算圆周率、图像处理、加密解密。开多个线程,让每个CPU核心都忙起来。
  • I/O密集型:读写文件、网络请求、数据库访问。开多个线程,在等待一个I/O操作时,让CPU去处理别的线程的任务。

2. 线程安全:最大的“坑”——“你的变量,大家的变量”

这是多线程最核心、最易出错的概念。多个线程同时访问同一个资源(变量、集合、文件等),如果不做任何保护,结果将是不可预知的。

// 一个经典的错误示例
private static int _counter = 0;void Main()
{for (int i = 0; i < 10; i++){// 启动10个线程,每个都对 _counter 加1000次new Thread(() => {for (int j = 0; j < 1000; j++)_counter++; // 这行不是原子操作!}).Start();}Thread.Sleep(2000);Console.WriteLine(_counter); // 你几乎永远得不到 10000!
}

为什么?因为 _counter++ 在底层其实是三步:读 -> 改 -> 写。线程A读完值(比如100)后,可能还没来得及写回,线程B也读了(也是100),然后两个线程都计算完写回,结果就成了101,而不是预期的102。

怎么办?加“锁”(Synchronization)

最常用的工具就是lock关键字(Monitor的语法糖)。

private static readonly object _lockObj = new object(); // 必须是一个私有、只读的引用对象
private static int _counter = 0;void Main()
{for (int i = 0; i < 10; i++){new Thread(() => {for (int j = 0; j < 1000; j++)lock(_lockObj) // 只有一个线程能进入这块代码{_counter++;}}).Start();}Thread.Sleep(2000);Console.WriteLine(_counter); // 现在稳稳的是 10000
}

记住:锁的对象应该是一个私有的、只读的引用类型对象,千万别用lock(this)lock(“string”)这种。


3. 现代多线程的利器:Task 和 async/await

别再一上来就new Thread()了!Thread是“底层工人”,创建和销毁成本高,不好管理。.NET 4.0 引入的 TPL(Task Parallel Library) 才是我们现在的主力。

  • Task:代表一个异步操作。它比Thread更轻量,底层用的是线程池,能高效地管理和复用线程,避免了频繁创建销毁线程的开销。

    // 用 Task 来执行后台计算
    Task.Run(() => {// 这里会在线程池线程中执行DoSomeHeavyCalculations();
    });
    
  • async/await(C# 5.0):这是异步编程的语法糖,它的主要目的是解放UI线程,保持界面响应,而不是直接创建新线程。

    // 在UI按钮点击事件中
    private async void btnDownload_Click(object sender, EventArgs e)
    {btnDownload.Enabled = false;// await 不会阻塞UI线程!// 它告诉编译器:等这个耗时的Task完成后再回来执行后面的代码,期间UI线程是自由的。string data = await HttpClient.GetStringAsync("http://example.com");// 这里会自动回到UI线程上下文,所以可以直接更新UItxtResult.Text = data;btnDownload.Enabled = true;
    }
    

    关键理解async/await本身不创建新线程。HttpClient.GetStringAsync这类I/O操作,大部分时间是在等待网络硬件,根本不需要占用任何CPU线程。它用了一种叫“IO完成端口”的高效机制。只有在遇到CPU密集型任务时,你才应该用Task.Run把它推到后台线程。


4. 并发集合:让你“锁”得更少一点

List<T>, Dictionary<TKey, TValue>这些集合都不是线程安全的。如果你每次都靠lock来保护它们,代码会很难写且容易死锁。

.NET 在 System.Collections.Concurrent 命名空间下提供了一堆现成的线程安全集合:

  • ConcurrentBag<T>: 一个无序的包,适合生产者-消费者场景。
  • ConcurrentDictionary<TKey, TValue>: 线程安全的字典,它的GetOrAdd, AddOrUpdate等方法非常强大且原子性。
  • BlockingCollection<T>: 一个带阻塞功能的集合,是实现生产者-消费者模式的绝佳工具。

用它们可以大大减少你手动lock的次数。


5. 取消操作:让线程“优雅地”退出

你不能直接粗暴地Abort()一个线程,这会导致资源泄露和状态不一致。正确的做法是使用协作式取消

.NET 提供了 CancellationTokenSourceCancellationToken 来实现这个模式。

void Main()
{var cts = new CancellationTokenSource();// 启动一个可取消的任务var task = Task.Run(() => DoWork(cts.Token), cts.Token);// 2秒后发出取消信号Thread.Sleep(2000);cts.Cancel();try { task.Wait(); } catch (AggregateException ex) { /* 处理取消异常 */ }
}void DoWork(CancellationToken token)
{while (true){token.ThrowIfCancellationRequested(); // 如果已取消,就抛出OperationCanceledException// ... 或者也可以这样检查if (token.IsCancellationRequested)break; // 优雅地清理并退出循环// 做一点工作Thread.Sleep(500);}
}

总结一下核心思想:

  1. 目的:榨干CPU性能,提升响应能力。
  2. 基石:理解线程安全,熟练使用lock
  3. 现代工具:抛弃原始的Thread,拥抱 Taskasync/await。分清 CPU密集型(用Task.Run)和 I/O密集型(用async/await)。
  4. 基础设施:使用并发集合减少锁的烦恼。
  5. 良好习惯:使用取消令牌实现优雅停止。

把这些点吃透,你就能解决95%的日常多线程问题了。剩下的就是一些高级主题,比如内存模型、信号量(SemaphoreSlim)、读写锁(ReaderWriterLockSlim)等,等遇到具体场景再深入研究也不迟。

http://www.wxhsa.cn/company.asp?id=5636

相关文章:

  • 基于MATLAB的图像融合拼接GUI系统设计
  • Python使用多线程和异步调用
  • 研究生学术英语读写教程(中国科学院大学出版) Unit10 TextA 原文以及翻译(仅供学习)
  • 基于Python+Vue开发的蛋糕商城管理系统源码+运行步骤
  • 某运营商智慧协同平台——构建高效、敏捷的运营管理新模式
  • go使用反射获取http.Request参数到结构体 - 实践
  • 基于MATLAB/Simulink的TI2000系列DSP模型设计
  • 挖矿木马病毒清理手册
  • nginx 常用参数
  • Python常见函数和代码示例
  • Java开发电脑开荒软件
  • 69-SQLite应用 - 详解
  • mysql 源码下载,从获取到安装的完整指南
  • docker中centos7配置
  • centos7虚拟机下系统环境配置
  • CefSharp高版本问题
  • 前缀和pre,如何求总和:pre(r) - pre(l)(1 = l = r = n),以及|pre(r) - pre(l)|
  • P11537 [NOISG 2023 Finals] Toxic Gene 题解
  • keil5中stm32相关记录
  • centos7中mysql环境配置
  • centos7中php环境配置
  • Symfony学习笔记 - 利用Doctrine开发一个学生信息的增删查改
  • 函数计算进化之路:AI Sandbox 新基座
  • linux通过smb共享文件夹,windows进行连接
  • 强制Apache Web服务器始终使用https
  • 初始vue3
  • 如何在Nginx服务器配置https以及强制跳转https
  • centos7中安装protobuf-c
  • 赞助NYU-Poly女性网络安全研讨会:推动行业多元发展
  • MyEMS:开源能源管理的探索与实践