在C#的多线程编程场景中,当多个线程同时访问和修改同一个共享资源时,很容易出现数据竞争问题,导致程序运行结果不符合预期,线程同步就是用来协调多个线程对共享资源的访问顺序,保证同一时间只有一个线程可以操作共享资源的技术方案。

C#线程同步的常用方法
1. lock语句
lock是C#中最常用的线程同步方式,它基于Monitor类实现,语法简洁,适合大多数简单的同步场景。使用lock时需要指定一个同步对象,通常建议使用私有的、引用类型的对象,避免使用公共对象或者值类型对象。
using System;
using System.Threading;
class Program
{
// 定义私有同步对象,避免使用公共对象引发意外同步
private static readonly object syncObj = new object();
private static int sharedCount = 0;
static void Main()
{
// 创建两个线程同时修改共享变量
Thread thread1 = new Thread(AddCount);
Thread thread2 = new Thread(AddCount);
thread1.Start();
thread2.Start();
thread1.Join();
thread2.Join();
Console.WriteLine($"最终共享计数:{sharedCount}");
}
static void AddCount()
{
for (int i = 0; i < 1000; i++)
{
// 使用lock包裹操作共享资源的代码
lock (syncObj)
{
sharedCount++;
}
}
}
}
2. Monitor类
Monitor类是lock语句的底层实现,相比lock它提供了更灵活的控制能力,比如可以尝试获取锁、设置锁的超时时间等。Monitor的核心方法包括Enter、Exit、TryEnter等。
using System;
using System.Threading;
class Program
{
private static readonly object syncObj = new object();
private static int sharedValue = 0;
static void Main()
{
Thread thread1 = new Thread(UpdateValue);
Thread thread2 = new Thread(UpdateValue);
thread1.Start();
thread2.Start();
thread1.Join();
thread2.Join();
Console.WriteLine($"最终共享值:{sharedValue}");
}
static void UpdateValue()
{
for (int i = 0; i < 500; i++)
{
// 尝试获取锁,超时时间设置为100毫秒
if (Monitor.TryEnter(syncObj, 100))
{
try
{
sharedValue++;
}
finally
{
// 确保锁一定会被释放
Monitor.Exit(syncObj);
}
}
else
{
Console.WriteLine("获取锁超时,本次操作跳过");
}
}
}
}
3. Mutex类
Mutex是互斥锁,它不仅可以在同一进程的多个线程之间实现同步,还可以跨进程使用,适合需要跨进程协调资源访问的场景。不过Mutex的性能比lock和Monitor要低,非必要场景不建议使用。
using System;
using System.Threading;
class Program
{
// 创建跨进程可用的互斥锁,名称为唯一标识
private static Mutex mutex = new Mutex(false, "Global\MyUniqueMutexName");
static void Main()
{
Thread thread1 = new Thread(DoWork);
Thread thread2 = new Thread(DoWork);
thread1.Start();
thread2.Start();
thread1.Join();
thread2.Join();
}
static void DoWork()
{
for (int i = 0; i < 300; i++)
{
// 等待获取互斥锁,无限等待
mutex.WaitOne();
try
{
Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId}正在操作资源");
Thread.Sleep(10);
}
finally
{
// 释放互斥锁
mutex.ReleaseMutex();
}
}
}
}
线程同步的注意事项
避免死锁
死锁是线程同步中最常见的问题,当两个或多个线程互相持有对方需要的锁,并且都在等待对方释放锁时,就会发生死锁。避免死锁的常见方式有:按照固定的顺序获取锁、设置锁的超时时间、减少锁的持有时间等。
控制同步粒度
同步粒度指的是被锁保护的代码范围,粒度太大会导致线程等待时间过长,降低程序并发性能;粒度太小可能无法完全保护共享资源。需要根据实际场景合理控制同步范围,只把操作共享资源的必要代码放在同步块中。
避免锁定公共对象
不要使用this、typeof(类名)等公共对象作为同步对象,因为这些对象可能被其他外部代码使用,容易引发意外的同步冲突,甚至死锁。建议始终使用私有的、专用的同步对象。
跨进程同步的选择
如果只需要在同一进程的线程之间同步,优先选择lock或者Monitor,性能更好;如果需要跨进程同步,再选择Mutex等支持跨进程的同步工具,不要盲目使用重量级的同步方式。