mutex与spinlock的区别和应用场景

这篇讲讲互斥锁(mutex)与自旋锁(spinlock)的差异,以及何种场景应该使用何种锁。
mutex是最经常用到的一个锁,当一个线程试着去锁住mutex失败了,就会自我陷入sleep状态,然后等待锁住这个mutex的另外一个线程unlock后,然后这个线程又被唤醒了。
spinlock,当试着lock失败后,会不停的轮询着尝试着去再次锁上,直到成功锁住。

互斥锁与自旋锁的应用场景

线程lock mutex失败sleep到最后wakeup,线程陷入sleep和wakeup会消耗一些额外的cpu指令。
mutex本身是一个内核对象,lock/unlock时都会用system call。而system call本身会占用至少几百个时钟周期,大概几微秒。

参考资料链接1中的说法如下:
For measuring the system-call overhead, getpid, the shortest Linux system call, was examined. To measure its cost under ideal circumstances, it was repeatedly invoked in a tight loop. Table 2 shows the consumed cycles and the time per invocation derived from the cycle numbers. The numbers were obtained using the cycle counter register of the Pentium processor.
Linux = 223 cycles = 1.68 µs (133MHz Pentium)

spinlock没有这些额外消耗,但是不停轮询过程会消耗一些额外的cpu指令。

  • 在多核机器中,如果锁住的“事务”很简单,占用很少的时间,就应该使用spinlock,这个时候spinlock的代价比mutex会小很多。”事务”很快执行完毕,自旋的消耗远远小于陷入sleep和wake的消耗。如果锁住“事务”粒度较大,就应该使用mutex,因为如果用spinlock,那么在“事务”执行过程中自旋很长时间还不如使得线程sleep。

  • 在单核机器中。spinlock没有任何意义的,spinlock只会浪费唯一核心的cpu时间片,这个时刻没有任何线程会运行的。所以单核机器中,不论锁住的”事务”的粒度大小都要使用

spinlock的例子代码如下

struct spinlock {
    int lock;
};

void spinlock_lock(struct spinlock *lock) {
    while (__sync_lock_test_and_set(&lock->lock,1)) {}
}

为什么spinlock循环中不能sleep或者yield

在看到spinlock的实现的时候的,当时就很疑惑为什么不能在循环内部加sleep之类的操作,陷入挂起状态交出cpu时间片。我们假设一种场景,在多核机器中有100个线程,1号线程拿到了锁,其他99个线程因为锁被1号线程获取了,所以陷入sleep,然后当1号线程执行完毕后,其他99个线程还是在挂起状态,所以1号线程继续拿到锁。于是导致其他99线程永远死循环,会饿死。
而且死循环每次调用sleep也会产生额外的很多指令,对性能影响也很大。

参考资料
https://www.ibm.com/developerworks/community/blogs/kevgrig/entry/approximate_overhead_of_system_calls9?lang=en
https://attractivechaos.wordpress.com/2011/10/06/multi-threaded-programming-efficiency-of-locking/
http://pages.cs.wisc.edu/~remzi/OSTEP/threads-locks.pdf

你可能感兴趣的:(mutex与spinlock的区别和应用场景)