简体   繁体   中英

Peterson's Algorithm on C++ multithreading

I've written a simple implementation for Peterson's Algorithm in C++ with multi threading. This program changes the string through two threads. But I'm not getting the final result. Where am I wrong?

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;
}

Prior to C++11 there was no threading model in C++. After C++11, your code does unordered access to the same variable causing race conditions.

Race conditions result in undefined behavior.

Changing a std::string is not atomic. You cannot do it safely while other threads are reading or writing from it.

In C++11 the threading primitives of std are a better idea than the above raw pthread code, excluding the very rare features you cannot emulate.

Refactored to use atomics. Note the explicit fences to guarantee correct ordering or reads/writes to the (non-atomic) string across threads.

Maybe someone would like to sanity-check my logic?

#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;
}

expected output:

wxyz

Endnote:

Modern memory architectures are not what they were when this algorithm was invented. Reads and writes to memory don't happen when you expect on a modern chip, and sometimes don't happen at all.

Cancel your appointments for the next 3 hours and watch this fantastic talk on the subject:

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

I had to make the threads wait till the thread first has finished, so I created separate threads, t for first and u for second and make main wait by pthread_join till the threads have finished. This removed the need to main spin.

Changes:

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;

The atomic fence in functions remained as they ensured ordered execution of instructions.

OUTPUT
abcd

and

Hello

And although changing the order of pthread_create of first and second always outputs to Hello , It kills the very idea of first thread waiting for second thread to complete. So I think the above changes will be the answer to it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM