2016-03-27 24 views
3

我写了一个简单的实现Peterson的C++多线程算法。该程序通过两个线程更改字符串。但我没有得到最终结果。我错在哪里? 彼得森的C++多线程算法

using namespace std; 

int flag[2]={0,1}; 
int turn; 

void* first(void* data){ 
    flag[0]=1; 
    turn=1; 
    while(flag[1] && turn==1){} 
    string &str=*(static_cast<string*>(data)); 
    if(str!=""){ 
     if(str=="abcd"){ 
      str="Hello"; 
     } 
    } 
    flag[0]=0; 
    pthread_exit(NULL); 
} 

void* second(void* data){ 
    flag[1]=1; 
    turn=0; 
    while(flag[0] && turn==0){} 
    string &str=*(static_cast<string*>(data)); 
    if(str!=""){ 
     if(str=="wxyz"){ 
      str="abcd"; 
     } 
    } 
    flag[1]=0; 
    pthread_exit(NULL); 
} 

int main(){ 
    int rc=0; 
    string s = "wxyz"; 
    pthread_t t; 

    rc=pthread_create(&t,NULL,first,static_cast<void*>(&s)); 
    if(rc!=0){ 
     cout<<"error!"; 
     exit(rc); 
    } 
    rc=pthread_create(&t,NULL,second,static_cast<void*>(&s)); 
    if(rc!=0){ 
     cout<<"error!"; 
     exit(rc); 
    } 

    while(flag[0] && flag[1]!=0){} 
    cout<<s; 

    pthread_exit(NULL); 
    return 0; 
} 

回答

0

我不得不做出线程等待,直到线程first已经完成,所以我创建单独的线程, tfirstusecond和使main等待pthread_join直到线程完成。这消除了旋转的需要main

变化:

pthread_t t,u; 
pthread_create(&t,NULL,first,static_cast<void*>(&s)); 
pthread_create(&u,NULL,second,static_cast<void*>(&s)); 
pthread_join(u,NULL); 
pthread_join(t,NULL); 
//while(flag[0] && flag[1]!=0){} 
cout<<s; 

函数的原子围栏仍然是他们保证了指令执行命令。

OUTPUT 
abcd 

Hello 

虽然改变的firstpthread_createsecond的顺序总是输出到Hello,它杀死第一个线程等待第二线程完成的想法。所以我认为上述变化将成为它的答案。

1

在C++ 11之前,C++中没有线程模型。在C++ 11之后,您的代码会无序访问导致竞争条件的相同变量。

竞态条件导致未定义的行为。

更改std::string不是原子的。当其他线程正在读取或写入时,您无法安全地进行操作。

在C++ 11中,std的线程原语比上面的原始pthread代码更好,不包括您无法模拟的非常罕见的功能。

+0

这样'std :: thread()'代码会比这更好吗? – Adnan

+2

@Adnan是的,但你也需要使'flag'和'turn' std :: atomic类型 - 加上同步访问字符串与互斥体。如果你不这样做,一个线程将不会以正确的顺序(或根本)“看到”另一个线程所做的更改。 –

+0

我已经包含了彼得森的算法来相互排除字符串访问,但没有使'flag'和'turn'原子。我也将字符串赋值转换为原子(如果可能的话),并发布结果 – Adnan

0

重构为使用原子。请注意显式的屏蔽以保证正确的排序或跨线程读取/写入(非原子)字符串。

也许有人想干净检查我的逻辑?

#include <iostream> 
#include <thread> 
#include <atomic> 
#include <memory> 

using namespace std; 

// atomic types require the compiler to issue appropriate 
// store-release/load-acquire ordering 
std::atomic<int> flag[2]={{0},{1}}; 
std::atomic<int> turn; 


void first(std::string& str){ 
    flag[0]=1; 
    turn=1; 
    while(flag[1] && turn==1){} 
    std::atomic_signal_fence(std::memory_order_acquire); 
    if(str!=""){ 
     if(str=="abcd"){ 
      str="Hello"; 
      std::atomic_signal_fence(std::memory_order_release); 
     } 
    } 
    flag[0]=0; 
} 

void second(std::string& str){ 
    flag[1]=1; 
    turn=0; 
    while(flag[0] && turn==0){} 
    std::atomic_signal_fence(std::memory_order_acquire); 
    if(str!=""){ 
     if(str=="wxyz"){ 
      str="abcd"; 
      std::atomic_signal_fence(std::memory_order_release); 
     } 
    } 
    flag[1]=0; 
} 

int main(){ 
    string s = "wxyz"; 

    auto t1 = std::thread(first, std::ref(s)); 
    auto t2 = std::thread(second, std::ref(s)); 

    for(; flag[0] && flag[1];) 
     ; 

    std::atomic_signal_fence(std::memory_order_acquire); 
    cout << s << endl; 

    t1.join(); 
    t2.join(); 
    return 0; 
} 

预期输出:

wxyz 

尾注:

现代存储架构是不是他们是什么时,该算法的发明。当你期望现代芯片时,读写内存不会发生,有时根本不会发生。

取消约会在接下来的3小时,观看关于这个问题这个梦幻般的谈话:

https://channel9.msdn.com/Shows/Going+Deep/Cpp-and-Beyond-2012-Herb-Sutter-atomic-Weapons-1-of-2

+0

删除原子'flag'声明上的'='符号。试了一下,得到了输出为'abcd'和'wxyz'。即使内存屏蔽也无法创建所需的结果。我会再提出一些建议,可能会对击剑进行更正 – Adnan

+0

我不明白为什么'“abcd”'不是合理的预期结果。有没有与主线程同步?如果两个线程都不启动,主线程可以打印?如果任何一个线程独自开始,它可以在没有争用的情况下进展 – Yakk

+0

@Yakk当任一标志为真时,主线程旋转。 –