C 彼得森锁算法的测试实现?

C 彼得森锁算法的测试实现?,c,concurrency,locking,c99,C,Concurrency,Locking,C99,有人知道C语言中的良好/正确实现吗?我好像找不到这个。谢谢。我不会断言该实现有多好或正确,但它已经过测试(简单)。这是维基百科上描述的算法的直接翻译 struct petersonslock_t { volatile unsigned flag[2]; volatile unsigned turn; }; typedef struct petersonslock_t petersonslock_t; petersonslock_t petersonslock () {

有人知道C语言中的良好/正确实现吗?我好像找不到这个。谢谢。

我不会断言该实现有多好或正确,但它已经过测试(简单)。这是维基百科上描述的算法的直接翻译

struct petersonslock_t {
    volatile unsigned flag[2];
    volatile unsigned turn;
};
typedef struct petersonslock_t petersonslock_t;

petersonslock_t petersonslock () {
    petersonslock_t l = { { 0U, 0U }, ~0U };
    return l;
}

void petersonslock_lock (petersonslock_t *l, int p) {
    assert(p == 0 || p == 1);
    l->flag[p] = 1;
    l->turn = !p;
    while (l->flag[!p] && (l->turn == !p)) {}
};

void petersonslock_unlock (petersonslock_t *l, int p) {
    assert(p == 0 || p == 1);
    l->flag[p] = 0;
};
格雷格指出,在内存一致性稍有放松的SMP体系结构(如x86)上,尽管对同一内存位置的加载是有序的,但对一个处理器上不同位置的加载可能对另一个处理器来说是无序的

Jens Gustedt和ninjalj建议修改原始算法以使用
原子标记
类型。这意味着设置标志和转弯将使用
原子标志测试和设置
,清除它们将使用C11中的
原子标志清除
。或者,可以在对
标志
的更新之间设置内存屏障

编辑:我最初试图通过将所有状态写入相同的内存位置来纠正此错误。ninjalj指出,按位操作将状态操作转化为RMW,而不是原始算法的加载和存储。因此,需要原子位运算。C11提供了这样的操作符,GCC也提供了内置操作符。下面的算法使用GCC内置,但包装在宏中,因此可以轻松地将其更改为其他实现。然而,修改上述原始算法是首选解决方案

struct petersonslock_t {
    volatile unsigned state;
};
typedef struct petersonslock_t petersonslock_t;

#define ATOMIC_OR(x,v)   __sync_or_and_fetch(&x, v)
#define ATOMIC_AND(x,v)  __sync_and_and_fetch(&x, v)

petersonslock_t petersonslock () {
    petersonslock_t l = { 0x000000U };
    return l;
}

void petersonslock_lock (petersonslock_t *l, int p) {
    assert(p == 0 || p == 1);
    unsigned mask = (p == 0) ? 0xFF0000 : 0x00FF00;
    ATOMIC_OR(l->state, (p == 0) ? 0x000100 : 0x010000);
    (p == 0) ? ATOMIC_OR(l->state, 0x000001) : ATOMIC_AND(l->state, 0xFFFF00);
    while ((l->state & mask) && (l->state & 0x0000FF) == !p) {}
};

void petersonslock_unlock (petersonslock_t *l, int p) {
    assert(p == 0 || p == 1);
    ATOMIC_AND(l->state, (p == 0) ? 0xFF00FF : 0x00FFFF);
};

Peterson的算法无法在C99中正确实现,如中所述

彼得森的算法如下:

LOCK:

interested[id] = 1                interested[other] = 1
turn = other                      turn = id

while turn == other               while turn == id
  and interested[other] == 1        and interested[id] == 1


UNLOCK:

interested[id] = 0                interested[other] = 0
这里有一些隐藏的假设。首先,每个线程必须在给出其轮次之前注意其获取锁的兴趣。放弃回合必须使我们感兴趣获取锁的另一个线程可见

此外,与在每个锁中一样,关键部分中的内存访问不能通过lock()调用提升,也不能通过unlock()调用下沉。即:lock()必须至少具有acquire语义,unlock()必须至少具有release语义

在C11中,实现这一点的最简单方法是使用顺序一致的内存顺序,这使得代码的运行就像是以程序顺序运行的线程的简单交错(警告:完全未测试的代码,但类似于Dmitry V'jukov的示例):

这确保编译器不会进行破坏算法的优化(通过在原子操作中提升/下沉加载/存储),并发出适当的CPU指令以确保CPU也不会破坏算法。C11/C++11原子操作的默认内存模型(不明确选择内存模型)是顺序一致内存模型

C11/C++11还支持较弱的内存模型,允许尽可能多的优化。以下是对C++11的C11翻译,该翻译最初由Dmitriy V'jukov使用他自己的Relacy Race Detector语法编写的算法 . 如果此算法不正确,则是我的错(警告:也是未经测试的代码,但基于Dmitriy V'jukov和Anthony Williams的良好代码):

请注意带有acquire和release语义的交换。交换就是交换 原子RMW操作。原子RMW操作总是读取存储的最后一个值 在RMW操作中写入之前。此外,原子对象上的捕获 从同一个原子对象(或任何更高版本)上的发行版读取写操作 从执行释放或任何更高版本的线程写入该对象 从任何原子RMW操作写入)创建与关系同步 在发布和获取之间

所以,这个操作是线程之间的一个同步点 始终与一个线程中的交换和 任何线程执行的最后一次交换(或turn的初始化,例如 第一次交流)

因此,我们在存储与感兴趣的[id] 和从/到
的交换转动
,a与两个之间的关系同步
连续交换从/到
回合
,并在关系之前排序 在从/到
的交换回合
和加载
感兴趣的[1-id]
之间。这 相当于访问中感兴趣的[x]
之前发生的关系 不同的线程,通过
turn
提供线程之间的同步。 这将强制执行使算法工作所需的所有排序

那么在C11之前这些事情是怎么做的呢?它涉及到使用编译器和 CPU特有的魔力。作为一个例子,让我们看看非常强序的x86。 IIRC,所有x86加载都有acquire语义,所有存储都有release 语义(保存非时态移动,在SSE中,精确地用于获得更高的 以牺牲性能为代价,本地需要发布CPU围栏以实现 CPU之间的一致性)。但这对于Peterson的算法来说是不够的,正如 Bartosz Milewsky在 , 为了使Peterson的算法起作用,我们需要在 访问
turn
感兴趣的
,如果不这样做,可能会导致看到加载 从
感兴趣的[1-id]
写入
感兴趣的[id]
之前,这是一件坏事

因此,在GCC/x86中实现这一点的一种方法是(警告:尽管我测试了类似于以下内容的内容,但实际上,的代码修改版本测试远不能保证多线程代码的正确性):

MFENCE
防止存储和加载到不同的内存地址 重新排序。否则,写入感兴趣的[id]
可能会在存储区中排队 在加载感兴趣的[1-id]的过程中进行缓冲。关于当前的许多问题 微体系结构a
SFENCElock(int id)
{
    atomic_store(&interested[id], 1);
    atomic_store(&turn, 1 - id);

    while (atomic_load(&turn) == 1 - id
           && atomic_load(&interested[1 - id]) == 1);
}

unlock(int id)
{
    atomic_store(&interested[id], 0);
}
lock(int id)
{
    atomic_store_explicit(&interested[id], 1, memory_order_relaxed);
    atomic_exchange_explicit(&turn, 1 - id, memory_order_acq_rel);

    while (atomic_load_explicit(&interested[1 - id], memory_order_acquire) == 1
           && atomic_load_explicit(&turn, memory_order_relaxed) == 1 - id);
}

unlock(int id)
{
    atomic_store_explicit(&interested[id], 0, memory_order_release);
}
lock(int id)
{
    interested[id] = 1;
    turn = 1 - id;
    __asm__ __volatile__("mfence");

    do {
        __asm__ __volatile__("":::"memory");
    } while (turn == 1 - id
           && interested[1 - id] == 1);
}

unlock(int id)
{
   interested[id] = 0;
}