1. 线程基础概念与核心原理
在C#多线程编程中,理解线程的本质是掌握高级线程技术的前提。线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。一个进程可以包含多个线程,这些线程共享进程的资源,但每个线程拥有独立的执行路径和栈空间。
1.1 线程的生命周期
C#中的线程生命周期包含以下几个关键状态:
- 新建状态(New):当创建Thread类实例但尚未调用Start方法时
- 就绪状态(Runnable):调用Start方法后,线程等待CPU时间片分配
- 运行状态(Running):线程获得CPU时间片,执行代码逻辑
- 阻塞状态(Blocked):线程等待某种资源(如I/O操作、锁等)时
- 终止状态(Terminated):线程执行完毕或异常终止
csharp复制Thread thread = new Thread(WorkerMethod); // New状态
thread.Start(); // 进入Runnable状态
// 当获得CPU时间片时进入Running状态
// 如果调用Sleep或等待锁则进入Blocked状态
// 方法执行完毕后进入Terminated状态
1.2 线程调度与优先级
Windows操作系统使用抢占式调度策略,C#线程优先级通过Thread.Priority属性设置,包含以下枚举值:
- Lowest
- BelowNormal
- Normal(默认值)
- AboveNormal
- Highest
注意:提高线程优先级可能导致其他线程"饥饿",应谨慎使用。通常只对关键任务线程适当提高优先级。
1.3 前台线程与后台线程
C#区分两种线程类型,直接影响应用程序的退出行为:
- 前台线程:会阻止进程终止,直到所有前台线程完成
- 后台线程:进程终止时会被强制结束,不考虑其执行状态
csharp复制Thread backgroundThread = new Thread(WorkerMethod);
backgroundThread.IsBackground = true; // 设置为后台线程
backgroundThread.Start();
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 线程创建与基本操作
2.1 创建线程的三种方式
2.1.1 ThreadStart委托
最基本的线程创建方式,适合无参数方法:
csharp复制void SimpleWorker() {
Console.WriteLine("线程执行中...");
}
Thread thread = new Thread(new ThreadStart(SimpleWorker));
thread.Start();
2.1.2 ParameterizedThreadStart委托
支持传递单个参数(object类型)给线程方法:
csharp复制void ParameterizedWorker(object data) {
int num = (int)data;
Console.WriteLine($"接收参数: {num}");
}
Thread thread = new Thread(new ParameterizedThreadStart(ParameterizedWorker));
thread.Start(42); // 传递参数
2.1.3 Lambda表达式
现代C#中最简洁的线程创建方式:
csharp复制Thread thread = new Thread(() => {
Console.WriteLine("Lambda表达式线程");
// 可以访问外部变量(需注意线程安全问题)
});
thread.Start();
2.2 线程控制方法
2.2.1 启动与停止
- Start():启动线程执行
- Abort()(已过时):强制终止线程(不推荐使用)
- Interrupt():中断处于Wait/Sleep/Join状态的线程
警告:Thread.Abort()方法在.NET Core/.NET 5+中已被移除,因为它可能导致资源泄漏和状态不一致。应使用协作式取消模式替代。
2.2.2 线程休眠与合并
- Sleep(int millisecondsTimeout):暂停当前线程指定时间
- Join():等待线程终止
- Join(int millisecondsTimeout):带超时的等待
csharp复制Thread worker = new Thread(() => {
Thread.Sleep(2000);
Console.WriteLine("工作线程完成");
});
worker.Start();
Console.WriteLine("主线程等待工作线程...");
worker.Join(3000); // 最多等待3秒
Console.WriteLine(worker.IsAlive ? "超时" : "工作线程已结束");
2.3 线程异常处理
线程内未捕获的异常会导致线程终止,但不会影响其他线程。最佳实践是在线程方法内部处理所有可能的异常:
csharp复制Thread thread = new Thread(() => {
try {
// 可能抛出异常的代码
}
catch (Exception ex) {
Console.WriteLine($"线程异常: {ex.Message}");
// 记录日志或采取恢复措施
}
});
thread.Start();
3. 线程同步与资源共享
3.1 竞态条件与线程安全
当多个线程访问共享资源时,可能产生竞态条件(Race Condition)。以下是一个典型示例:
csharp复制int counter = 0;
void UnsafeIncrement() {
for (int i = 0; i < 100000; i++) {
counter++; // 非原子操作
}
}
Thread t1 = new Thread(UnsafeIncrement);
Thread t2 = new Thread(UnsafeIncrement);
t1.Start(); t2.Start();
t1.Join(); t2.Join();
Console.WriteLine(counter); // 通常小于200000
3.2 锁机制
3.2.1 lock关键字
C#中最简单的同步机制:
csharp复制private readonly object _lockObj = new object();
int counter = 0;
void SafeIncrement() {
for (int i = 0; i < 100000; i++) {
lock (_lockObj) {
counter++;
}
}
}
最佳实践:使用专用object实例作为锁对象,避免锁定this或Type对象。
3.2.2 Monitor类
lock语句的底层实现,提供更灵活的控制:
csharp复制Monitor.Enter(_lockObj);
try {
// 临界区代码
}
finally {
Monitor.Exit(_lockObj);
}
3.2.3 Mutex
系统级互斥锁,可用于跨进程同步:
csharp复制using var mutex = new Mutex(false, "Global\\MyAppMutex");
try {
mutex.WaitOne();
// 临界区代码
}
finally {
mutex.ReleaseMutex();
}
3.3 高级同步原语
3.3.1 Semaphore
限制同时访问资源的线程数量:
csharp复制Semaphore semaphore = new Semaphore(3, 3); // 最多3个线程同时访问
void ResourceAccess() {
semaphore.WaitOne();
try {
// 访问受限资源
}
finally {
semaphore.Release();
}
}
3.3.2 ReaderWriterLockSlim
优化读写场景,允许多个读或单个写:
csharp复制ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();
void ReadData() {
rwLock.EnterReadLock();
try {
// 读取共享数据
}
finally {
rwLock.ExitReadLock();
}
}
void WriteData() {
rwLock.EnterWriteLock();
try {
// 修改共享数据
}
finally {
rwLock.ExitWriteLock();
}
}
3.3.3 Barrier
协调多个线程在某个点同步:
csharp复制Barrier barrier = new Barrier(3); // 等待3个线程
void PhaseWork() {
// 阶段1工作
barrier.SignalAndWait(); // 等待所有线程完成阶段1
// 阶段2工作
}
4. 线程池与任务并行库(TPL)
4.1 ThreadPool基础
.NET提供了线程池来高效管理线程资源:
csharp复制ThreadPool.QueueUserWorkItem(state => {
Console.WriteLine($"线程池线程ID: {Thread.CurrentThread.ManagedThreadId}");
});
线程池特点:
- 自动管理线程创建和回收
- 限制最大线程数(防止资源耗尽)
- 适合短时间任务
4.2 Task类
TPL提供了更高级的抽象:
csharp复制Task.Run(() => {
Console.WriteLine("Task运行在线程池线程上");
});
Task<int> taskWithResult = Task.Run(() => {
Thread.Sleep(1000);
return 42;
});
Console.WriteLine($"结果: {taskWithResult.Result}"); // 阻塞直到获取结果
4.3 异步等待模式
现代C#推荐使用async/await而非直接创建线程:
csharp复制async Task<int> CalculateAsync() {
await Task.Delay(1000); // 非阻塞延迟
return await Task.Run(() => {
// CPU密集型计算
return 123;
});
}
// 调用
int result = await CalculateAsync();
5. 线程安全集合与模式
5.1 并发集合
System.Collections.Concurrent命名空间提供了线程安全集合:
- ConcurrentQueue
- ConcurrentStack
- ConcurrentDictionary<TKey,TValue>
- ConcurrentBag
- BlockingCollection
csharp复制ConcurrentQueue<int> queue = new ConcurrentQueue<int>();
// 生产者
Task.Run(() => {
for (int i = 0; i < 100; i++) {
queue.Enqueue(i);
Thread.Sleep(10);
}
});
// 消费者
Task.Run(() => {
while (true) {
if (queue.TryDequeue(out int item)) {
Console.WriteLine($"处理: {item}");
}
}
});
5.2 生产者-消费者模式
使用BlockingCollection实现经典模式:
csharp复制BlockingCollection<int> buffer = new BlockingCollection<int>(boundedCapacity: 10);
// 生产者
Task producer = Task.Run(() => {
for (int i = 0; i < 100; i++) {
buffer.Add(i);
Console.WriteLine($"生产: {i}");
}
buffer.CompleteAdding();
});
// 消费者
Task consumer = Task.Run(() => {
foreach (int item in buffer.GetConsumingEnumerable()) {
Console.WriteLine($"消费: {item}");
Thread.Sleep(50);
}
});
Task.WaitAll(producer, consumer);
5.3 不可变数据结构
对于读多写少的场景,不可变集合是更好的选择:
csharp复制ImmutableList<int> list = ImmutableList<int>.Empty;
// 每次"修改"都返回新实例
ImmutableList<int> newList = list.Add(1).Add(2).Add(3);
// 原始集合保持不变
Console.WriteLine(list.Count); // 输出0
Console.WriteLine(newList.Count); // 输出3
6. 线程调试与性能分析
6.1 调试多线程应用
Visual Studio提供强大的多线程调试工具:
- 线程窗口(Debug > Windows > Threads)
- 并行堆栈窗口(Debug > Windows > Parallel Stacks)
- 任务窗口(Debug > Windows > Tasks)
调试技巧:
- 使用Thread.Name属性为线程命名
- 设置断点条件(如Thread.CurrentThread.ManagedThreadId == x)
- 使用Debugger.Break()在代码中触发断点
6.2 性能分析工具
- Visual Studio性能分析器:检测CPU使用率、线程争用等
- PerfView:微软提供的免费性能分析工具
- dotnet-counters:CLI工具监控实时指标
关键性能指标:
- 上下文切换次数
- 锁争用率
- 线程池排队任务数
- 线程创建/销毁频率
6.3 常见性能问题与优化
-
锁争用:
- 症状:大量线程等待同一个锁
- 解决方案:减小锁粒度、使用读写锁、考虑无锁数据结构
-
过度线程创建:
- 症状:频繁创建/销毁线程,高上下文切换开销
- 解决方案:使用线程池、合理设置线程池大小
-
虚假共享:
- 症状:多个CPU核心频繁访问同一缓存行的不同数据
- 解决方案:内存填充、调整数据结构布局
csharp复制// 避免虚假共享的示例
[StructLayout(LayoutKind.Explicit, Size = 64)] // 缓存行通常64字节
struct PaddedCounter {
[FieldOffset(0)]
public long counter1;
// 填充剩余空间
[FieldOffset(32)]
public long counter2;
}
7. 高级线程模式与最佳实践
7.1 取消模式
使用CancellationToken实现协作式取消:
csharp复制CancellationTokenSource cts = new CancellationTokenSource();
Task longRunningTask = Task.Run(() => {
while (true) {
cts.Token.ThrowIfCancellationRequested();
// 执行工作
Thread.Sleep(100);
}
}, cts.Token);
// 在需要时取消
cts.CancelAfter(2000);
try {
await longRunningTask;
}
catch (OperationCanceledException) {
Console.WriteLine("任务已取消");
}
7.2 双阶段终止模式
优雅停止线程的标准模式:
csharp复制class Worker {
private volatile bool _shutdownRequested;
private Thread _workerThread;
public void Start() {
_workerThread = new Thread(DoWork);
_workerThread.Start();
}
public void Stop() {
_shutdownRequested = true;
_workerThread.Join(); // 等待线程结束
}
private void DoWork() {
while (!_shutdownRequested) {
// 执行工作
Thread.Sleep(100);
}
// 清理资源
}
}
7.3 线程局部存储
ThreadStatic和ThreadLocal
csharp复制[ThreadStatic]
static int _perThreadCounter;
// 或者使用ThreadLocal<T>
ThreadLocal<int> threadLocalCounter = new ThreadLocal<int>(() => 0);
void Work() {
_perThreadCounter++;
threadLocalCounter.Value++;
Console.WriteLine($"ThreadStatic: {_perThreadCounter}, ThreadLocal: {threadLocalCounter.Value}");
}
// 在不同线程中调用Work()会看到独立的值
7.4 最佳实践总结
- 避免直接创建线程:优先使用线程池、Task或async/await
- 最小化同步范围:锁只保护必要的数据,尽快释放
- 避免死锁:按固定顺序获取多个锁,或使用带超时的锁
- 考虑不可变性:不可变对象天生线程安全
- 合理设置线程优先级:通常保持Normal,特殊需求才调整
- 处理所有异常:防止线程静默失败
- 使用适当粒度的锁:过粗降低并发性,过细增加开销
- 监控线程数量:防止线程爆炸(Thread Pool Starvation)
- 避免阻塞线程池线程:特别是I/O操作应使用异步API
- 测试多线程场景:特别注意竞态条件和死锁
