Peterson 的解决方案实现在 C 语言中不起作用



我有以下代码,我试图用它来理解彼得森的解决方案。当我为小值的循环运行此实现直到 9999 时,输出正确显示为 0,但是当我使用更高的循环值(如 9999999(对此进行测试时,我得到的值接近 0,而不是 0,是否有可能在 (*(a))++; 部分中执行递增和递减线程?为什么下面的实现不起作用?我的程序中有任何错误吗?

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#define LOOP 9999999
int interest[2] = {0,0};
int turn = 0;
int loops[2] = {LOOP,LOOP};
void increment(int *a) {
  printf("Incrementing %pn",a);
  for(int i=0;i<LOOP;i++) {
    interest[0] = 1;
    turn = 1;
    while(interest[1] == 1 && turn == 1);
    (*(a))++;
    loops[0]--;
    interest[0] = 0;
  }
}
void decrement(int *a) {
  printf("Decrementing %pn",a);
  for(int i=0;i<LOOP;i++) {
    interest[1] = 1;
    turn = 0;
    while(interest[0] == 1 && turn == 0);
    (*(a))--;
    loops[1]--;
    interest[1] = 0;
  }
}
void print_val(int *a) {
  while(1) {
    getchar();
    printf("value at mem %dniInc(%d) iDec(%d)n",*a,loops[0],loops[1]);
  }
}
int main() {
  pthread_t t1, t2, t3;
  int *mem = malloc(sizeof(int));
  pthread_create(&t1, NULL, (void*)decrement, (void*)mem);
  pthread_create(&t2, NULL, (void*)increment, (void*)mem);
  pthread_create(&t3, NULL, (void*)print_val, (void*)mem);
  pthread_join(t1,NULL);  
  pthread_join(t2,NULL);  
  printf("operation completen");
  pthread_join(t3,NULL);  
  return 0;
}

输出:

$:~/Arena/sem $ gcc op.c -pthread && ./a.out
Incrementing 0xd16010
Decrementing 0xd16010
operation complete
value at mem -2
iInc(0) iDec(0)
^Z
[13]+  Stopped                 ./a.out
$:~/Arena/sem $ gcc op.c -pthread && ./a.out
Decrementing 0x2432010
Incrementing 0x2432010
operation complete
value at mem 16
iInc(0) iDec(0)
^Z
[14]+  Stopped                 ./a.out
meow:~/Arena/sem $ 

编辑:

  1. 我已经尝试了彼得森算法的错误实现? 并且添加易失性没有帮助,我也没有像上一个线程中所说的那样使用 ++ 操作。
  2. 彼得森解决方案的实现无法正常工作,而 Java 中的彼得森算法?不在 C 中,我的线程不是骗子。

看看答案:

大多数答案都表明编译器可能会进行一些重新排序,我已经添加了程序集转储,有人可以帮助我了解这两个过程如何最终进入关键部分吗?

递增功能

 for(int i=0;i<LOOP;i++) {
  25:   c7 45 f4 00 00 00 00    mov    DWORD PTR [ebp-0xc],0x0
  2c:   eb 50                            jmp    7e <increment+0x7e>
    interest[0] = 1;
    turn = 1;
  2e:   c7 05 00 00 00 00 01   mov    DWORD PTR ds:0x0,0x1
  35:   00 00 00 
    while(interest[1] == 1 && turn == 1);
  38:   c7 05 00 00 00 00 01   mov    DWORD PTR ds:0x0,0x1
  3f:   00 00 00 
    //while(turn == 1 && interest[1] == 1);
  42:   90                                 nop
  43:   a1 04 00 00 00             mov    eax,ds:0x4
  48:   83 f8 01                        cmp    eax,0x1
  4b:   75 0a                            jne    57 <increment+0x57>
  4d:   a1 00 00 00 00             mov    eax,ds:0x0
  52:   83 f8 01                        cmp    eax,0x1
  55:   74 ec                             je     43 <increment+0x43>
    (*(a->location))++;
  57:   8b 45 08                        mov    eax,DWORD PTR [ebp+0x8]
  5a:   8b 00                             mov    eax,DWORD PTR [eax]
  5c:   8b 10                             mov    edx,DWORD PTR [eax]
  5e:   83 c2 01                        add    edx,0x1
  61:   89 10                             mov    DWORD PTR [eax],edx
    loops[0]--;
  63:   a1 00 00 00 00              mov    eax,ds:0x0
  68:   83 e8 01                        sub    eax,0x1
  6b:   a3 00 00 00 00              mov    ds:0x0,eax
    interest[0] = 0;
  70:   c7 05 00 00 00 00 00    mov    DWORD PTR ds:0x0,0x0
  77:   00 00 00 

递减功能

for(int i=0;i<LOOP;i++) {
  8f:   8b 45 08                       mov    eax,DWORD PTR [ebp+0x8]
  92:   8b 50 04                      mov    edx,DWORD PTR [eax+0x4]
  95:   8b 45 08                      mov    eax,DWORD PTR [ebp+0x8]
  98:   8b 00                           mov    eax,DWORD PTR [eax]
  9a:   89 54 24 08                 mov    DWORD PTR [esp+0x8],edx
  9e:   89 44 24 04                 mov    DWORD PTR [esp+0x4],eax
  a2:   c7 04 24 28 00 00 00  mov    DWORD PTR [esp],0x28
  a9:   e8 fc ff ff ff                 call   aa <decrement+0x21>
    interest[1] = 1;
  ae:   c7 45 f4 00 00 00 00   mov    DWORD PTR [ebp-0xc],0x0
  b5:   eb 4f                            jmp    106 <decrement+0x7d>
    turn = 0;
    while(interest[0] == 1 && turn == 0);
  b7:   c7 05 04 00 00 00 01  mov    DWORD PTR ds:0x4,0x1
  be:   00 00 00 
    //while(turn == 0 && interest[0] == 1);
  c1:   c7 05 00 00 00 00 00  mov    DWORD PTR ds:0x0,0x0
  c8:   00 00 00 
    (*(a->location))--;
  cb:   90                                nop
  cc:   a1 00 00 00 00            mov    eax,ds:0x0
  d1:   83 f8 01                      cmp    eax,0x1
  d4:   75 09                           jne    df <decrement+0x56>
  d6:   a1 00 00 00 00            mov    eax,ds:0x0
  db:   85 c0                           test   eax,eax
  dd:   74 ed                           je     cc <decrement+0x43>
    loops[1]--;
  df:   8b 45 08                       mov    eax,DWORD PTR [ebp+0x8]
  e2:   8b 00                            mov    eax,DWORD PTR [eax]
  e4:   8b 10                            mov    edx,DWORD PTR [eax]
  e6:   83 ea 01                       sub    edx,0x1
  e9:   89 10                            mov    DWORD PTR [eax],edx
    interest[1] = 0;
  eb:   a1 04 00 00 00             mov    eax,ds:0x4
  f0:   83 e8 01                        sub    eax,0x1
  f3:   a3 04 00 00 00              mov    ds:0x4,eax

您似乎正在使用 pthreads,这是 POSIX 标准的一部分。 POSIX 不允许你像这样"滚动自己的"同步原语 - 它在 4.11 内存同步中有这样说:

应用程序应确保通过更多人访问任何内存位置 超过一个控制线程(线程或进程(受到限制,例如 任何控制线程都无法读取或修改内存位置,而 另一个控制线程可能正在修改它。这种访问是 使用同步线程执行的函数进行限制,并且还 相对于其他线程同步内存。

这样做的实际结果是,允许编译器进行转换并执行重新排序,这可能会破坏您的假设。

例如,我拥有的编译器将while()循环优化为无限循环,因为它可以看到turn在设置它和在循环中测试它之间不能合法地修改,因为没有调用 POSIX 同步函数。 这显然不会发生在你身上,但其他类似的问题也是可能的。

在这种情况下,可能不是编译器优化在咬你,而是不尊重 CPU 内存模型。 本文介绍 Peterson 锁如何在多处理器 x86 上要求内存围栏正确。 (POSIX 同步原语的实现将包括必要的内存围栏,但您的代码不包含(。

就您的代码而言,interest[1]的负载可以在写入interest[0]之前由 x86 重新排序,而interest[0]的负载同样可以在写入 interest[1] 之前重新排序。 这意味着两个线程可以同时看到interest[0] == 0interest[1] == 0,并且都进入关键部分。

我不确定 POSIX,但至少在标准 C11 中,您需要使用 atomic_int 来转弯和感兴趣。不volatile.

我也可能弄错了(只知道 c++ 的规则(,但如果您在打印operation complete之前使用它,print_val函数中的不同步访问可能会导致未定义的行为。

在读取/写入变量interest(内存位置interest[0]interest[1](、turn*a时,您有竞争条件。 即递增和递减线程都对这些变量进行不同步的访问。 print_val也会访问*a,而其他线程可能正在更新它。

您需要一种同步机制,例如对这些变量的互斥锁或原子访问,以便程序正常工作。

相关内容

  • 没有找到相关文章

最新更新