C++ 多线程-彼得森&x27;s算法不起作用
这里我使用Peterson算法来实现互斥 我有两个非常简单的线程,一个将计数器增加1,另一个将计数器减少1C++ 多线程-彼得森&x27;s算法不起作用,c++,c,multithreading,algorithm,C++,C,Multithreading,Algorithm,这里我使用Peterson算法来实现互斥 我有两个非常简单的线程,一个将计数器增加1,另一个将计数器减少1 const int PRODUCER = 0,CONSUMER =1; int counter; int flag[2]; int turn; void *producer(void *param) { flag[PRODUCER]=1; turn=CONSUMER; while(flag[CONSUMER] && turn==CONSUMER)
const int PRODUCER = 0,CONSUMER =1;
int counter;
int flag[2];
int turn;
void *producer(void *param)
{
flag[PRODUCER]=1;
turn=CONSUMER;
while(flag[CONSUMER] && turn==CONSUMER);
counter++;
flag[PRODUCER]=0;
}
void *consumer(void *param)
{
flag[CONSUMER]=1;
turn=PRODUCER;
while(flag[PRODUCER] && turn==PRODUCER);
counter--;
flag[CONSUMER]=0;
}
只要我运行一次,它们就可以正常工作
但当我在循环中再次运行它们时,奇怪的事情就会发生
这是我的主功能
int main(int argc, char *argv[])
{
int case_count =0;
counter =0;
while(counter==0)
{
printf("Case: %d\n",case_count++);
pthread_t tid[2];
pthread_attr_t attr[2];
pthread_attr_init(&attr[0]);
pthread_attr_init(&attr[1]);
counter=0;
flag[0]=0;
flag[1]=0;
turn = 0;
printf ("Counter is intially set to %d\n",counter);
pthread_create(&tid[0],&attr[0],producer,NULL);
pthread_create(&tid[1],&attr[1],consumer,NULL);
pthread_join(tid[0],NULL);
pthread_join(tid[1],NULL);
printf ("counter is now %d\n",counter);
}
return 0;
}
我一次又一次地运行这两个线程,直到有一次计数器不是零
然后,在几个案例之后,程序将始终停止!在数百起案件之后,有时在数千起案件之后,或者在数万起事件之后
这意味着在一种情况下,计数器不是零。但是为什么???这两个线程在关键会话中修改计数器,并且只增加和减少计数器一次。为什么计数器不是零
然后我在其他计算机上运行这段代码,更奇怪的事情发生了——在一些计算机上,程序似乎没有问题,而其他计算机也有同样的问题!为什么?
顺便说一句,在我的电脑中,我在虚拟机软件的虚拟电脑Ubuntu 16.04中运行这段代码。其他人的电脑也是Ubuntu 16.04,但并不是所有的都在虚拟机中。有问题的计算机同时包含虚拟机和真实机。实现任何类型的线程安全算法都需要硬件支持
有很多原因导致代码不能按预期的方式工作。最简单的一个是核心有单独的缓存。所以你的程序从两个核心开始。两个缓存标志都必须为0,0。他们都修改了自己的副本,所以他们看不到另一个核心在做什么
此外,内存是分块工作的,因此写入标志[PRODUCER]很可能也会写入标志[CONSUMER](因为INT是4字节,而当今大多数处理器的内存块是64字节)
另一个问题是操作重新排序。编译器和处理器都可以交换指令。有一些约束规定单线程执行结果不应更改,但显然它们不适用于这里
编译器可能还发现您正在将turn设置为x,然后检查它是否为x,这在单线程世界中显然是正确的,因此可以对其进行优化
这份清单并非详尽无遗。还有很多事情(某些特定于平台)可能会发生并破坏您的程序
因此,至少尝试使用std::atomic类型和强内存顺序(memory_order_seq_cst)。所有变量都应该是std::atomic。这为您提供了硬件支持,但速度会慢得多
这仍然不起作用,因为大多数情况下,您可能仍然有一些代码,您可以在其中阅读然后进行更改。这不是原子性的,因为在读取数据之后和更改数据之前,其他线程可能已经更改了数据。彼得森的算法仅适用于单核处理器/单CPU系统
那是因为他们不做真正的并行处理。两个atomar操作永远不会同时获得executet
如果您有两个或多个CPU/CPU核心,那么可以同时执行的atomar操作的数量将增加一个CPU(核心)。
这意味着,即使整数赋值是atomar,也可以在不同的CPU/内核中同时执行多次
在您的情况下,turn=CONSUMER/PRODUCER
只是在不同的CPU/内核中同时调用两次
取消所有CPU核心(只有一个用于您的程序),它应该可以正常工作。我只看了一眼,但是:两个线程访问共享的非原子变量,而没有互斥体保护-这可能会导致不确定性。现在,其他效果开始发挥作用(即随机效果)。每个线程在哪个内核上运行?它们是以实并发还是伪并发方式运行?缓存是否同步?变量是否缓冲在(不同的)寄存器中?(对于独立于系统的)语句的执行顺序是否已更改?这些都是你无法回答的问题。这可能很有趣:@Scheff是的,我想你是对的。两个拖线都修改变量turn
。我给的修改添加了一个锁,一切正常。但这是否意味着彼得森的算法是错误的?“但这是否意味着彼得森的算法是错误的?”我不这么认为。他可能没有给出C或C++的代码。换句话说,他假设或定义了额外的补助金,这些资金不是C或C++所提供的。看一看。这也很好。我还在寻找一个幻灯片(在谷歌上),我曾经用它来澄清我的想法……最终找到了:。你能更具体地解释一下为什么彼得森的算法不能在多处理器系统上工作吗?为什么要在两个CPU内核中执行atomar操作?在执行atomar命令时,CPU内核在comand完成之前不会执行任何上下文更改。这就是彼得森算法有效的原因。如果有两个线程,并且每个线程都在另一个内核中执行,那么它们可以同时运行。因此,每个线程都可以独立于另一个线程执行自己的命令,这意味着如果不同内核中的两个线程同时执行同一行atomar代码,它们的行为就好像它们在非原子指令中被调度程序交换一样。多核处理器的目的就是独立于其他线程执行代码彼此这就是为什么他们这么快。但是从现在开始,你需要跨CPU的同步机制,比如信号量或者像互斥体一样使用它们的技术。如果同时调用turn=CONSUMER/PRODUCER
,是否意味着一个核心turn
为0,另一个为1?和计数器+++
将被调用两次?但在我的实践中,当程序停止时,计数器最终是-1或1,而不是2,为什么?我认为类似的情况可能会发生(c=计数器;rp=生产者CPU寄存器;rc=消费者CPU寄存器):计数器