线程同步概述
知识点
什么是线程同步
线程同步是多线程编程中的核心概念,指的是协调多个线程对共享资源的访问,确保数据的一致性和程序的正确性。
为什么需要线程同步
竞态条件(Race Condition):多个线程同时访问共享资源时可能产生不确定的结果
数据不一致:没有同步机制时,多线程可能导致数据状态不一致
内存可见性:一个线程对共享变量的修改对其他线程不一定立即可见
常见的同步问题
死锁(Deadlock):两个或多个线程相互等待对方释放资源
活锁(Livelock):线程不断重试但无法取得进展
饥饿(Starvation):某些线程长时间无法获得所需资源
.NET中的同步机制分类
基础同步原语:lock、Monitor、Mutex
信号通知:AutoResetEvent、ManualResetEvent、Semaphore
读写同步:ReaderWriterLock、ReaderWriterLockSlim
原子操作:Interlocked、volatile
高级同步:Barrier、CountdownEvent、SemaphoreSlim
代码案例
案例1:没有同步的竞态条件
using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
private static int counter = 0;
static void Main(string[] args)
{
// 启动多个任务同时修改共享变量
Task[] tasks = new Task[10];
for (int i = 0; i < 10; i++)
{
tasks[i] = Task.Run(() =>
{
for (int j = 0; j < 1000; j++)
{
counter++; // 非原子操作,可能产生竞态条件
}
});
}
Task.WaitAll(tasks);
Console.WriteLine($"期望结果: 10000");
Console.WriteLine($"实际结果: {counter}"); // 通常小于10000
Console.WriteLine("由于竞态条件,实际结果可能小于期望结果");
}
}
案例2:使用lock解决同步问题
using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
private static int counter = 0;
private static readonly object lockObject = new object();
static void Main(string[] args)
{
Task[] tasks = new Task[10];
for (int i = 0; i < 10; i++)
{
tasks[i] = Task.Run(() =>
{
for (int j = 0; j < 1000; j++)
{
lock (lockObject) // 使用lock确保线程安全
{
counter++;
}
}
});
}
Task.WaitAll(tasks);
Console.WriteLine($"期望结果: 10000");
Console.WriteLine($"实际结果: {counter}"); // 总是等于10000
Console.WriteLine("使用lock后,结果正确");
}
}
案例3:使用Interlocked进行原子操作
using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
private static int counter = 0;
static void Main(string[] args)
{
Task[] tasks = new Task[10];
for (int i = 0; i < 10; i++)
{
tasks[i] = Task.Run(() =>
{
for (int j = 0; j < 1000; j++)
{
Interlocked.Increment(ref counter); // 原子递增操作
}
});
}
Task.WaitAll(tasks);
Console.WriteLine($"期望结果: 10000");
Console.WriteLine($"实际结果: {counter}"); // 总是等于10000
Console.WriteLine("使用Interlocked.Increment确保了原子性");
}
}
案例4:演示死锁问题
using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
private static readonly object lock1 = new object();
private static readonly object lock2 = new object();
static void Main(string[] args)
{
Console.WriteLine("演示死锁问题...");
Task task1 = Task.Run(() =>
{
lock (lock1)
{
Console.WriteLine("Task1: 获得了lock1");
Thread.Sleep(1000); // 模拟一些工作
lock (lock2)
{
Console.WriteLine("Task1: 获得了lock2");
}
}
});
Task task2 = Task.Run(() =>
{
lock (lock2)
{
Console.WriteLine("Task2: 获得了lock2");
Thread.Sleep(1000); // 模拟一些工作
lock (lock1)
{
Console.WriteLine("Task2: 获得了lock1");
}
}
});
// 等待5秒,如果任务未完成则可能发生了死锁
if (!Task.WaitAll(new[] { task1, task2 }, TimeSpan.FromSeconds(5)))
{
Console.WriteLine("检测到死锁!任务未能在5秒内完成。");
}
else
{
Console.WriteLine("任务成功完成,没有发生死锁。");
}
}
}
知识点总结
线程同步的重要性:在多线程环境中,线程同步是确保程序正确性的关键机制
常见同步问题:
竞态条件:多线程并发访问共享资源导致的不确定结果
死锁:线程间相互等待造成的僵局
活锁和饥饿:资源分配不当导致的性能问题
同步机制选择:
简单互斥:使用lock或Monitor
原子操作:使用Interlocked类
复杂同步:使用专门的同步原语
性能考虑:
同步会带来性能开销
选择合适的同步机制很重要
避免过度同步
最佳实践:
尽量减少共享状态
保持锁的粒度适当
避免嵌套锁以防止死锁
使用超时机制防止无限等待