我是多线程编程的新手,我尝试在C中编写Bakery Lock Algorithm代码。
以下是代码:
int number[N]; // N is the number of threads
int choosing[N];
void lock(int id) {
choosing[id] = 1;
number[id] = max(number, N) + 1;
choosing[id] = 0;
for (int j = 0; j < N; j++)
{
if (j == id)
continue;
while (1)
if (choosing[j] == 0)
break;
while (1)
{
if (number[j] == 0)
break;
if (number[j] > number[id]
|| (number[j] == number[id] && j > id))
break;
}
}
}
void unlock(int id) {
number[id] = 0;
}
然后我运行以下示例。我运行100个线程,每个线程运行以下代码:
for (i = 0; i < 10; ++i) {
lock(id);
counter++;
unlock(id);
}
执行完所有线程后,共享counter
的结果为10 * 100 = 1000
,这是预期值。我多次执行我的程序,结果总是1000
。所以似乎锁的实现是正确的。基于previous question {I},这看起来很奇怪,因为我没有使用任何内存屏障/栅栏。 我是幸运吗?
然后我想创建一个使用许多不同锁的多线程程序。所以我创建了这个(完整的代码可以找到here):
typedef struct {
int number[N];
int choosing[N];
} LOCK;
并且代码更改为:
void lock(LOCK l, int id)
{
l.choosing[id] = 1;
l.number[id] = max(l.number, N) + 1;
l.choosing[id] = 0;
...
现在,在执行我的计划时,有时我会997
,有时会998
,有时会1000
。所以锁定算法不正确。
我做错了什么?我该怎么做才能解决它?
现在我正在从number
读取数组choosing
和struct
,这可能是一个问题
那不是原子的东西吗?
我应该使用内存防护,如果是这样的话(我尝试在代码的各个方面使用asm("mfence")
,但它没有帮助)?
答案 0 :(得分:1)
使用pthreads
,标准规定访问一个线程中的varable而另一个线程正在或可能正在修改它是未定义的行为。您的代码遍布整个地方。例如:
while (1)
if (choosing[j] == 0)
break;
此代码在等待另一个线程修改它时反复访问choosing[j]
。编译器完全可以自由修改此代码,如下所示:
int cj=choosing[j];
while(1)
if(cj == 0)
break;
为什么呢?因为标准很清楚,当该线程可能正在访问它时,另一个线程可能无法修改该变量,因此可以假设该值保持不变。但显然,这不会起作用。
它也可以这样做:
while(1)
{
int cj=choosing[j];
if(cj==0) break;
choosing[j]=cj;
}
相同的逻辑。编译器写回变量是否合法是完全合法的,只要它在代码可以访问变量时这样做。 (因为在那个时候,另一个线程修改它是不合法的,所以值必须相同而且写入是无害的。在某些情况下,写入确实是一个优化,现实世界的代码有被这些回写打破了。)
如果要编写自己的同步函数,则必须使用具有适当原子性和内存可见性语义的原始函数构建它们。您必须遵守规则,否则您的代码将失败,并且会以可怕和不可预测的方式失败。