为何在并发基准测试中全局锁性能优于全序锁?
我在完成操作系统作业时,对比了两种死锁避免的实现方案:全局锁方案(vector-avoid-hold-and-wait.c)和全序锁方案(vector-global-order.c)。原本以为多了一把全局锁的方案会因为额外的锁竞争导致性能更差,但实际测试结果显示,随着线程数增加,全局锁方案的耗时反而明显低于全序锁方案,想搞清楚背后的原因。
两种方案的核心实现
1. 全局锁方案
void vector_add(vector_t *v_dst, vector_t *v_src) { // put GLOBAL lock around all lock acquisition... Pthread_mutex_lock(&global); Pthread_mutex_lock(&v_dst->lock); Pthread_mutex_lock(&v_src->lock); Pthread_mutex_unlock(&global); int i; for (i = 0; i < VECTOR_SIZE; i++) { v_dst->values[i] = v_dst->values[i] + v_src->values[i]; } Pthread_mutex_unlock(&v_dst->lock); Pthread_mutex_unlock(&v_src->lock); }
2. 全序锁方案
void vector_add(vector_t *v_dst, vector_t *v_src) { if (v_dst < v_src) { Pthread_mutex_lock(&v_dst->lock); Pthread_mutex_lock(&v_src->lock); } else if (v_dst > v_src) { Pthread_mutex_lock(&v_src->lock); Pthread_mutex_lock(&v_dst->lock); } else { // special case: src and dst are the same Pthread_mutex_lock(&v_src->lock); } int i; for (i = 0; i < VECTOR_SIZE; i++) { v_dst->values[i] = v_dst->values[i] + v_src->values[i]; } Pthread_mutex_unlock(&v_src->lock); if (v_dst != v_src) Pthread_mutex_unlock(&v_dst->lock); }
测试结果
# 全局锁方案耗时 ./vector-avoid-hold-and-wait -t -n 16 -l 100000 -d Time: 0.29 seconds ./vector-avoid-hold-and-wait -t -n 32 -l 100000 -d Time: 0.36 seconds ./vector-avoid-hold-and-wait -t -n 64 -l 100000 -d Time: 0.74 seconds ./vector-avoid-hold-and-wait -t -n 99 -l 100000 -d Time: 1.11 seconds # 全序锁方案耗时 ./vector-global-order -t -n 16 -l 100000 -d Time: 0.25 seconds ./vector-global-order -t -n 32 -l 100000 -d Time: 0.54 seconds ./vector-global-order -t -n 64 -l 100000 -d Time: 1.35 seconds ./vector-global-order -t -n 99 -l 100000 -d Time: 1.95 seconds
性能差异的核心原因
1. 锁等待模式导致的上下文切换开销不同
全序锁方案看起来是细粒度锁,但实际执行时,线程会先获取第一个vector锁,然后尝试获取第二个。如果第二个锁被其他线程占用,当前线程会持有第一个锁的同时进入阻塞状态。这种情况下,操作系统会触发上下文切换,把CPU让给其他线程,而第一个锁会被一直持有,导致其他需要这个锁的线程也阻塞,形成连锁等待,上下文切换的开销会随着线程数增加快速累积。
而全局锁方案是先获取全局锁,一次性拿到两个vector锁后立刻释放全局锁。线程等待的是全局锁,而不是持有一个锁等待另一个。全局锁的持有时间极短(只有两次Pthread_mutex_lock的时间),所以全局锁的竞争开销很小,但避免了“持有锁等待”的情况,大幅减少了上下文切换的次数。
2. 全局锁的实际开销远低于预期
你之前担心的“多一把锁带来的额外开销”其实被高估了。全局锁只是用来保证锁的获取顺序,它的持有时间非常短,线程拿到全局锁后几乎立刻就能拿到两个vector锁并释放全局锁。在高并发场景下,这种短持有时间的锁竞争带来的开销,远低于全序锁方案中频繁的上下文切换和锁持有等待的开销。
3. 高线程数下的放大效应
当线程数较少时(比如16线程),全序锁的上下文切换次数不多,所以性能和全局锁接近。但随着线程数增加,全序锁中“持有一个锁等待另一个”的冲突概率呈指数级上升,上下文切换的开销被不断放大,而全局锁方案的开销增长则平缓得多,最终在高线程数下反超全序锁。
内容的提问来源于stack exchange,提问作者肯安定

