C 当在结构内部使用时,面包房锁不会';行不通
我是多线程编程新手,我试着用C编写代码 代码如下:C 当在结构内部使用时,面包房锁不会';行不通,c,multithreading,locking,pthreads,C,Multithreading,Locking,Pthreads,我是多线程编程新手,我试着用C编写代码 代码如下: int number[N]; // N is the number of threads int choosing[N]; void lock(int id) {
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;
}
代码更改为:
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
并从struct
中选择,这可能是个问题吗
那不是原子还是什么
我应该使用内存围栏吗?如果是的话,应该在哪些点使用内存围栏(我尝试在代码的各个点上使用asm(“mfence”)
,但没有帮助)对于pthreads
,标准规定在一个线程中访问变量,而另一个线程正在或可能正在修改它是未定义的行为。你的代码到处都是这样的。例如:
while (1)
if (choosing[j] == 0)
break;
此代码在等待另一个线程修改它时,反复访问选择[j]
。编译器完全可以按如下方式修改此代码:
int cj=choosing[j];
while(1)
if(cj == 0)
break;
为什么??因为标准很清楚,当该线程可能正在访问变量时,另一个线程可能不会修改该变量,所以可以假定该值保持不变。但很明显,这是行不通的
它也可以做到这一点:
while(1)
{
int cj=choosing[j];
if(cj==0) break;
choosing[j]=cj;
}
同样的逻辑。编译器写回一个变量是完全合法的,不管它是否被修改过,只要它在代码可以访问该变量时这样做。(因为,在当时,另一个线程修改它是不合法的,所以值必须相同,并且写操作是无害的。在某些情况下,写操作实际上是一种优化,而真实世界的代码已经被这种写回破坏了。)
如果要编写自己的同步函数,必须使用具有适当原子性和内存可见性语义的基本函数来构建它们。您必须遵守规则,否则您的代码将失败,并且失败得可怕且不可预测。对于pthreads
,标准规定,在一个线程中访问变量,而另一个线程正在或可能正在修改它是未定义的行为。你的代码到处都是这样的。例如:
while (1)
if (choosing[j] == 0)
break;
此代码在等待另一个线程修改它时,反复访问选择[j]
。编译器完全可以按如下方式修改此代码:
int cj=choosing[j];
while(1)
if(cj == 0)
break;
为什么??因为标准很清楚,当该线程可能正在访问变量时,另一个线程可能不会修改该变量,所以可以假定该值保持不变。但很明显,这是行不通的
它也可以做到这一点:
while(1)
{
int cj=choosing[j];
if(cj==0) break;
choosing[j]=cj;
}
同样的逻辑。编译器写回一个变量是完全合法的,不管它是否被修改过,只要它在代码可以访问该变量时这样做。(因为,在当时,另一个线程修改它是不合法的,所以值必须相同,并且写操作是无害的。在某些情况下,写操作实际上是一种优化,而真实世界的代码已经被这种写回破坏了。)
如果要编写自己的同步函数,必须使用具有适当原子性和内存可见性语义的基本函数来构建它们。您必须遵守规则,否则您的代码将失败,并且失败得可怕且不可预测。我希望这只是一个学术练习,而不是您实际尝试使用的东西…@R。。我不会在实践中使用面包店的锁:)。这也不是练习。我只是想习惯如何使用多线程程序。@Fooko R。如果是这样,不要实现像锁定自己这样的原语。使用标准的、有效的和经验证的API/库。我的观点是,这个面包店的锁是如此低效和不可伸缩,你不应该这样做。大多数尝试使用自己的锁的人都对系统库提供的锁的性能不满意,因此我认为使用原子类型使用自己的低级锁是有意义的(特别是在使用C11的情况下),但是,编写一个总是比实际系统提供的锁慢几十倍或几百倍的锁是没有意义的。我希望这只是一个学术练习,而不是你实际试图使用的东西…@R。。我不会在实践中使用面包店的锁:)。这也不是练习。我只是想习惯如何使用多线程程序。@Fooko R。如果是这样,不要实现像锁定自己这样的原语。使用标准的、有效的和经验证的API/库。我的观点是,这个面包店的锁是如此低效和不可伸缩,你不应该这样做。大多数尝试使用自己的锁的人都对系统库提供的锁的性能不满意,因此我认为使用原子类型使用自己的低级锁是有意义的(特别是在使用C11的情况下),但是编写一个总是比实际系统提供的锁慢几十倍或几百倍的锁是没有意义的。或者使用C11或编译器特定的-pre-C11原子。@R。。问题是,我不知道有哪个平台可以保证volatile
正常工作。而确认它确实起作用的过程比它的价值要麻烦得多。“这似乎是可行的,在某些情况下,这可能足够好了。”DavidSchwartz感谢您的回答。但是,如果我使用例如asm(“mfence::“memory”)
我会避免您在示例中给出的这种内存重新排序吗?如果这是未定义的行为,那么所有并发数据结构是如何创建的?例如,在列表中,您将无法读取列表的标题
,因为其他线程可能会更改它(进行插入)。@FookoR。您可以使用锁、原子操作或其他提供所需语义的同步设备。(在您的情况下,互斥体和条件变量将