旋转锁实现(OSSpinLock)

时间:2012-10-18 07:12:23

标签: c multithreading macos thread-safety

我刚开始研究多线程编程和线程安全性。我熟悉忙碌等待,经过一些研究后,我现在熟悉自旋锁背后的理论,所以我想我会看一下OSSpinLock在Mac上的实现。它归结为以下函数(在objc-os.h中定义):

static inline void ARRSpinLockLock(ARRSpinLock *l)
{
again:
   /* ... Busy-waiting ... */
    thread_switch(THREAD_NULL, SWITCH_OPTION_DEPRESS, 1);
    goto again;
}

Full implementation here

在进行了一些挖掘之后,我现在已经大致了解thread_switch的参数是什么(this site是我发现它的地方)。我对我所读到的内容的解释是,对thread_switch的这一特定调用将切换到下一个可用线程,并将当前线程的优先级降低到1个周期的绝对最小值。 '最终'(在CPU时间内)此线程将再次变为活动状态并立即执行goto again;指令,从而重新开始忙等待。

我的问题是,为什么这个电话实际上是必要的?我发现了一个自旋锁的另一个实现(对于Windows这次)here并且它根本不包括(Windows等效的)线程切换调用。

2 个答案:

答案 0 :(得分:3)

您可以通过多种不同方式实现自旋锁。如果您找到针对Windows的其他SpinLock实施,则会看到另一种算法(可能涉及SetThreadPrioritySleepSwitchToThread)。

ARRSpinLockLock的默认实现非常聪明,在第一个旋转周期后,它“暂停”一段时间的线程优先级,这有以下优点:

  • 它为发布拥有锁的线程提供更多机会;
  • 执行NOPPAUSE会浪费更少的CPU时间(以及权力!)。

Windows实施不会这样做,因为Windows API没有提供这样的机会(没有等效的thread_switch()功能,并且对SetThreadPriority 的多次调用可能效率低下)。

答案 1 :(得分:1)

我实际上并不认为他们有那么不同。在第一种情况下:

static inline void ARRSpinLockLock(ARRSpinLock *l)
{
    unsigned y;
again:
    if (__builtin_expect(__sync_lock_test_and_set(l, 1), 0) == 0) {
        return;
    }
    for (y = 1000; y; y--) {
#if defined(__i386__) || defined(__x86_64__)
        asm("pause");
#endif
        if (*l == 0) goto again;
    }
    thread_switch(THREAD_NULL, SWITCH_OPTION_DEPRESS, 1);
    goto again;
}

我们试图获得锁定。如果失败了,我们会在for循环中旋转,如果它在此期间可用,我们会立即尝试重新获取它,否则我们会放弃CPU。

在另一种情况下:

inline void Enter(void)
{
    int prev_s;
    do
    {
        prev_s = TestAndSet(&m_s, 0);
        if (m_s == 0 && prev_s == 1)
        {
            break;
        }
        // reluinquish current timeslice (can only
        // be used when OS available and
        // we do NOT want to 'spin')
        // HWSleep(0);
    }
    while (true);
}

请注意if下面的注释,它实际上表示如果操作系统为我们提供该选项,我们可以旋转或放弃CPU。事实上,第二个例子似乎只是将这部分留给了程序员[在这里插入你继续使用代码的首选方式],所以从某种意义上讲,它不像第一个那样完整的实现。

我对整个事情的看法,我正在评论第一个片段,是他们试图在能够快速获得锁定(1000次迭代)和不过多地占用CPU之间取得平衡(因此,如果锁不可用,我们最终会切换。)