

新闻资讯
技术学院Lock Contention 指线程等待进入锁临界区的总阻塞时间,非锁内执行耗时;高值表明多线程争抢同一锁,引发调度开销与CPU空转,是典型并发瓶颈。
它不是指某次 lock 语句执行耗时,而是指线程在等待进入 lock 临界区时被阻塞的总时间(单位通常是毫秒或微秒)。这个指标高,说明多个线程频繁争抢同一把锁,导致大量线程挂起、调度切换、CPU 空转——这是典型的并发瓶颈信号。
EventPipe 事件采集,但需勾选 Concurrency 或 Threading 事件集,否则该指标为空根本原因不是用了 lock,而是锁的粒度、持有时间和竞争范围不合理。常见高风险模式:
private static readonly object _lock = new(); 作为全类型共享锁,所有实例方法都串行执行lock 块里调用外部服务(如 HTTP 请求、DB 查询)、IO 操作或长时间计算lock (_list)),而实际只需保护某次 Add/Remove示例中这段代码极易触发高 contention:
private static readonly object _sharedLock = new();
public void ProcessItem(Item item)
{
lock (_sharedLock) // ❌ 所有线程挤在这儿排队
{
var data = _httpClient.GetStringAsync(item.Url).GetAwaiter().GetResult(); // 阻塞 IO!
_cache[item.Id] = Process(data); // 复杂计算也在这里面
}
}
不能只看总量,要结合调用栈和锁对象标识定位根因:
ToString() 或哈希 ID)System.Object 实例,可通过其内存地址在 “Memory Usage” 视图中反查分配位置(需开启内存分配采样)lock 前打点 DateTime.UtcNow.Ticks,释放后计算差值并记录 >10ms 的情况(临时诊断用)this 或装箱值类型作锁对象——它们难以追踪且易引发意外共享很多场景根本不需要 lock。优先考虑无锁或细粒度同步原语:
ReaderWriterLockSlim 替代全局 lock,允许多个读线程并发Interlocked.Increment(ref _count) 或 ConcurrentDictionary
ConcurrentQueue、ConcurrentStack,内部已做无锁优化lock (_locks[item.Id % _locks.Length])
真正难的是判断“是否真的需要同步”。比如缓存填充逻辑,常可用 Lazy 或 ConcurrentDictionary.GetOrAdd() 消除显式锁。
锁争用本身不难发现,难的是确认它是否掩盖了更深层的设计问题:比如本该异步处理的流程被强行同步化,或者状态不该跨线程共享却做了共享。盯着 “Lock Contention” 数字调优,不如先问一句:这把锁,真的必要吗?