C# 线程同步方法 C#如何使用lock关键字

来源:这里教程网 时间:2026-02-21 17:39:53 作者:

lock 关键字必须作用于引用类型对象

直接对

int
bool
或值类型变量加
lock
会编译失败,因为
lock
要求表达式结果是引用类型。常见错误写法:
int counter = 0;
lock (counter) { /* 编译错误:不能将 int 用作 lock 表达式 */ }
正确做法是声明一个专用的私有只读对象字段:
private readonly object _lockObj = new object();
// ...
lock (_lockObj) { counter++; }
别用
this
typeof(MyClass)
或公共字段——它们可能被外部代码锁定,导致死锁或意外阻塞。

lock 不是万能的,它只保证临界区串行执行

lock
只确保同一把锁对象保护的代码块不会被多个线程同时进入,但它不解决以下问题:

锁外的共享变量读写仍可能引发竞态(比如先判断再修改的“检查-执行”逻辑) 锁粒度太粗会严重拖慢吞吐;太细则容易漏锁或重复锁 无法跨进程同步,也不能替代异步等待(
await
内部不能用
lock
例如下面这段看似安全的代码实际有问题:
if (list.Count == 0) {
    lock (_lockObj) {
        if (list.Count == 0) { // 必须双重检查!
            list.Add(item);
        }
    }
}
漏掉内层判断,就可能在两次
Count
读取之间被其他线程插入元素,导致重复添加。

lock 和 Monitor.Enter/Exit 的关系

lock (obj) { ... }
是语法糖,编译后等价于调用
Monitor.Enter(obj)
Monitor.Exit(obj)
,并包裹在
try/finally
中。这意味着:

即使临界区内抛出异常,锁也会被释放(这是
lock
安全的核心)
手动调用
Monitor.Enter
时若忘记配对
Monitor.Exit
,会导致永久死锁
Monitor.TryEnter(obj, timeout)
可实现带超时的获取锁,而
lock
不支持
所以除非需要超时或条件等待(
Monitor.Wait
/
Pulse
),否则优先用
lock

lock 与 async/await 不能共存

async
方法里直接写
lock
会编译报错:
public async Task DoWorkAsync() {
    lock (_lockObj) { // ❌ CS1996:无法在异步方法中使用 lock 语句
        await Task.Delay(100);
    }
}
原因是
lock
依赖线程上下文连续性,而
await
可能切换线程。替代方案包括:

SemaphoreSlim
(注意用
await semaphore.WaitAsync()
+
finally { semaphore.Release(); }
把需要同步的纯 CPU 操作抽离到同步方法中,在
await
前/后调用
改用不可变数据结构或无锁并发集合(如
ConcurrentQueue<t></t>
别试图用
Task.Run(() => { lock (...) { ... } })
来“绕过”,这只会增加线程开销且不解决根本问题。

相关推荐