自旋锁陷阱

之前ibireme大神写过一篇OSSpinLock不再安全的文章(链接)。文中虽然提到的是OSSpinLock,但实际上,问题的原因是由于自旋锁本身的特性导致的。

不同于NSLock、pthread_mutex为代表的互斥锁,OSSpinLock等自旋锁在竞争资源已经被占有时,自身所在线程并没有像互斥锁那样停止运行,并在资源被释放后被被动的唤醒,而是一直在主动的轮询竞争资源是否被释放。因此,一旦竞争资源被释放,自身线程就可以立即得以继续运行,省去了被动唤醒的时间。这也就是各种锁的百万次加锁、释放锁性能比较中,自旋锁的性能能够优于互斥锁的原因。

但也相应的,这种线程并不挂起而是持续轮询的策略,在和线程优先级调度算法的配合中,会导致OSSpinLock不再安全的问题。

下面是每个步骤的解释:

  • Step 1:低优先级的线程A开始运行,并加锁占有了竞争资源继续运行。
  • Step 2:高优先级的线程B开始运行,并请求被占有的竞争资源。但由于竞争资源已经被占有,再加上自旋锁的特性,线程B并没有挂起,而是在持续的轮询请求。此时,因为线程B的优先级是要高于线程A的,优先级调度算法就会挂起线程A,让线程B得以继续运行。但由于竞争资源还是在被线程A所占有的,所以线程B就永远无法请求成功。至此,就形成了一个死循环。

事实上这个问题和优先级翻转一样,都是优先级调度算法和锁的配合问题,解决方法也很类似,诸如优先级继承、优先级天花板、时间片天花板等。不过这些工作都是在底层内核上的了,我们日常的上层业务工作中,由于OSSpinLock的问题似乎还没有正式宣布解决,所以暂时还是避免使用OSSpinLock,用性能稍欠的pthread_mutex替代最为保险。