lock关键字通过Monitor类实现线程互斥,确保同一时间仅一个线程执行临界区代码,防止竞态条件;推荐使用私有、静态、只读的引用类型对象作为锁,以避免死锁和同步失效;适用于保护共享数据、单例初始化、外部资源访问等场景,但在高并发下存在性能瓶颈、死锁风险及无法区分读写等问题;可选用ReaderWriterLockSlim、SemaphoreSlim、Interlocked、并发集合等替代方案以提升性能和灵活性。

C# 中的
lock
lock
System.Threading.Monitor
lock (expression)
Monitor.Enter(expression)
try/finally
Monitor.Exit(expression)
expression
object
工作原理是这样的:当一个线程尝试进入
lock
expression
lock
lock
lock
一个非常重要的实践是,你锁定一个私有的、静态的、只读的 object
来看一个简单的例子:
public class Counter
{
private readonly object _lockObject = new object(); // 推荐的锁对象
private int _count;
public void Increment()
{
lock (_lockObject) // 确保每次只有一个线程能修改 _count
{
_count++;
Console.WriteLine($"Current count: {_count}");
}
}
public int GetCount()
{
// 读取操作也可能需要锁定,取决于业务逻辑和对数据一致性的要求
// 如果_count的读取和写入是分离的,且读取不要求最新状态,可以不加锁
// 但如果要求读取的是最新写入的值,或者读取本身涉及复杂操作,则仍需加锁
lock (_lockObject)
{
return _count;
}
}
}lock
在我看来,
lock
比如说,一个简单的
i++
i
i
i++
i
i
i
i
i
i
结果
i
lock
lock
i
lock
我个人觉得,
lock
保护共享内存中的数据结构: 这是最常见的场景。比如,你有一个静态的
Dictionary<string, object>
List<T>
lock
private static readonly object _cacheLock = new object();
private static Dictionary<string, string> _dataCache = new Dictionary<string, string>();
public static void AddOrUpdateCache(string key, string value)
{
lock (_cacheLock)
{
_dataCache[key] = value;
}
}
public static string GetFromCache(string key)
{
lock (_cacheLock)
{
return _dataCache.TryGetValue(key, out var value) ? value : null;
}
}你看,无论是写入还是读取,都通过同一个锁对象来协调,避免了潜在的冲突。
管理单例模式的实例创建: 在多线程环境下,确保单例模式只创建一个实例是很有挑战性的。虽然现在有了
Lazy<T>
lock
public class Singleton
{
private static Singleton _instance;
private static readonly object _lock = new object();
private Singleton() { } // 私有构造函数
public static Singleton GetInstance()
{
if (_instance == null) // 第一次检查
{
lock (_lock) // 加锁
{
if (_instance == null) // 第二次检查
{
_instance = new Singleton();
}
}
}
return _instance;
}
}这种模式利用
lock
对外部资源的独占访问: 当你的应用程序需要访问一个外部资源,比如文件、数据库连接池中的某个连接,或者一个串口设备时,如果这个资源不支持并发访问,那么你就需要用
lock
控制特定逻辑流程的原子性: 有时候,你可能不只是要保护一个变量,而是要确保一系列相关的操作作为一个整体,不被其他线程打断。比如,一个复杂的业务逻辑涉及到多个步骤,这些步骤必须连续执行才能保证数据状态的正确性。
lock
总的来说,当并发操作涉及对共享状态的修改,且这些修改必须是互斥的、原子性的,并且你对性能要求不是极端苛刻,同时锁的粒度可以接受时,
lock
lock
尽管
lock
lock
性能瓶颈: 这是最显而易见的。
lock
死锁风险:
lock
object lockA = new object();
object lockB = new object();
// 线程1
lock (lockA)
{
Thread.Sleep(100); // 模拟工作
lock (lockB) { /* ... */ }
}
// 线程2
lock (lockB)
{
Thread.Sleep(100); // 模拟工作
lock (lockA) { /* ... */ }
}这种交叉锁定很容易导致死锁。解决死锁通常需要严格遵循锁的获取顺序,或者使用更复杂的策略。
无法区分读写操作:
lock
不支持超时:
lock
替代方案:
面对
lock
Monitor
lock
Monitor
Monitor.Enter()
Monitor.Exit()
Monitor.TryEnter()
TryEnter
Monitor.Wait()
Monitor.Pulse()
PulseAll()
ReaderWriterLockSlim
SemaphoreSlim
SemaphoreSlim
lock
Mutex
lock
Mutex
Interlocked
Interlocked
并发集合(System.Collections.Concurrent
ConcurrentDictionary<TKey, TValue>
ConcurrentQueue<T>
ConcurrentBag<T>
lock
任务并行库 (TPL) 和 async/await
async/await
选择哪种同步机制,往往取决于具体的场景、对性能的要求以及对复杂度的容忍度。
lock
ReaderWriterLockSlim
Monitor
以上就是C#的lock关键字如何实现线程同步?适用场景是什么?的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号