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.