【问题标题】:Linux 3.0: futex-lock deadlock bug?Linux 3.0:futex-lock 死锁错误?
【发布时间】:2012-03-08 16:35:05
【问题描述】:
// SubFetch(x,y) = atomically x-=y and return x (__sync_sub_and_fetch)
// AddFetch(x,y) = atomically x+=y and return x (__sync_add_and_fetch)
// CompareWait(x, y) = futex(&x, FUTEX_WAIT, y) wait on x if x == y
// Wake(x, y) = futex(&x, FUTEX_WAKE, y) wake up y waiters

struct Lock
{
Lock() : x(1) {}

void lock()
{
    while (true)
    {
        if (SubFetch(x, 1) == 0)
            return;

        x = -1;

        CompareWait(x, -1);
    }
}

void unlock()
{
    if (AddFetch(x, 1) == 1)
        return;

    x = 1;

    Wake(x, 1);
}

private:
    int x;
};

Linux 3.0 提供了一个名为 futex 的系统调用,许多并发实用程序都基于该调用,包括最近的 pthread_mutex 实现。每当您编写代码时,您都应该始终考虑使用现有实现还是自己编写代码是您项目的更好选择。

上面是一个基于futex和man futex(7)中语义描述的Lock(互斥量,1个允许计数信号量)的实现

它似乎包含一个死锁错误,在多个线程尝试锁定和解锁它几千次后,线程可能会进入 x == -1 并且所有线程都卡在 CompareWait 中的状态,但是没有人正在持有锁。

任何人都可以看到错误在哪里吗?

更新:我有点惊讶 futex(7)/semantics 如此糟糕。我完全重写了Lock如下......现在这样正确吗?

// CompareAssign(x,y,z) atomically: if (x == y) {x = z; ret true; } else ret false;

struct Lock
{
Lock() : x(0) {}

void lock()
{
    while (!CompareAssign(x, 0, 1))
        if (x == 2 || CompareAssign(x, 1, 2))
            CompareWait(x, 2);
}

void unlock()
{
    if (SubFetch(x, 1) == 0)
        return;

    x = 0;

    Wake(x, 1);
}

private:
int x;
};

这里的想法是x有以下三种状态:

0: unlocked
1: locked & no waiters
2: locked & waiters

【问题讨论】:

  • 我听说 futexes 看起来很简单,很难正确使用它们。你这样做有什么理由吗?
  • 嗯? pthread_mutex 有什么问题?
  • futex sematic “这么破”是什么意思。它是用户空间原子操作和基于内核的线程等待和信号之间的接口。实现这一点并非常有效地做到这一点是微不足道的。它并不意味着是应用程序级编程 API。
  • 正如 futex 文档所述,它会受到虚假唤醒的影响,您是否检查并正确处理 EINTR ?
  • @JensGustedt:我的意思是 futex(7)/“语义”部分中的实现描述不正确并且充满了竞争条件。您的其余评论无关紧要。

标签: c++ c linux concurrency futex


【解决方案1】:

问题在于,如果SubFetch 未能获得锁,您将显式-1 分配给x。这与解锁赛跑。

  1. 线程 1 获取锁。 x==0
  2. 线程 2 尝试获取锁。 SubFetchx 设置为 -1,然后线程 2 被挂起。
  3. 线程 1 释放锁。 AddFetchx 设置为 0,因此代码随后将 x 显式设置为 1 并调用 Wake
  4. 线程 2 唤醒并将 x 设置为 -1,然后调用 CompareWait

线程 2 现在处于等待状态,x 设置为 -1,但周围没有人唤醒它,因为线程 1 已经释放了锁。

【讨论】:

  • 我明显误读了 futex(7),上面写着“应该将计数器设置为 -1”。解决方法是什么?
  • justsoftwaresolutions.co.uk/files/c_mutexes.zip 有一个基于 futex 的互斥锁的代码基本上,你不能盲目地设置x 的值,你必须使用原子比较和交换操作,并检查所有情况下的返回值。
【解决方案2】:

Ulrich Drepper 的论文“Futexes are tricky”中描述了基于 futex 的 Mutex 的正确实现

http://people.redhat.com/drepper/futex.pdf

它不仅包括代码,而且还非常详细地解释了为什么它是正确的。论文中的代码:

class mutex
{
 public:
 mutex () : val (0) { }
 void lock () {
   int c;
   if ((c = cmpxchg (val, 0, 1)) != 0)
     do {
       if (c == 2 || cmpxchg (val, 1, 2) != 0)
         futex_wait (&val, 2);
     } while ((c = cmpxchg (val, 0, 2)) != 0);
 }
 void unlock () {
//NOTE: atomic_dec returns the value BEFORE the operation, unlike your SubFetch !
   if (atomic_dec (val) != 1) {
     val = 0;
     futex_wake (&val, 1);
   }
 }
 private:
   int val;
};

将论文中的代码与您的代码进行比较,我发现了不同

你有

if (x == 2 || CompareAssign(x, 1, 2))

直接使用 futex 的值,而 Drepper 使用前一个 CompareAssign() 的返回值。这种差异可能只会影响性能。

您的解锁代码也不同,但在语义上似乎是等价的。

无论如何,我强烈建议您严格遵守 Drepper 的代码。那篇论文经受住了时间的考验,并获得了很多同行评议。自己滚动不会有任何收获。

【讨论】:

    【解决方案3】:

    这个场景有三个线程,A、B 和 C。

    这个场景的初始状态有:

    • 线程 A 持有锁
    • 线程 B 还没有竞争锁
    • CompareWait() 中的线程 C
    • x == -1 从 C 获取锁失败时开始
    甲乙丙 ============================================== AddFetch() (所以 x == 0) 子提取() (所以 x == -1) x = 1 x = -1 唤醒()

    此时无论B还是C都解封了,SubFetch()时都不会得到0的结果。

    【讨论】:

    • 好吧,要么我完全误解了 futex(7) 的语义部分,要么描述完全被破坏了。这里有什么解决办法?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-20
    • 2021-10-08
    相关资源
    最近更新 更多