Posix thread is used, if the code needs to be run on many platforms. C++ std::thread doesn't work reliably on every platform yet. It has severe performance bottlenecks.
Let's first create a simple thread.
#include <iostream> #include <thread> using namespace std;// Thread myThread will execute this function void processAThread1(int val){ cout << "Hello from Thread1!" << endl; cout << "Argument passed in from main(): " << val << endl; } int main(){// constructs a new thread mythread and runs it thread myThread(&processAThread1, 100); //thread object is myThread/* Makes the main thread to wait
for the new thread to finish execution */ myThread.join(); cout << "Hello from my main()" << endl; return 0; }
Output:
shirley:~/Documents/lab_assignments/cpp/thread$ g++ -std=c++17 thread1.cpp -o prog -lpthread shirley:~/Documents/lab_assignments/cpp/thread$ ./prog Hello from Thread1! Argument passed in from main(): 100 Hello from my main()
Now, I wanted to create multiple threads and access the critical section by writing a concurrent program. In this section, concurrent access is protected by Mutex. The mutex
class is a synchronization primitive that can be used to protect shared data from being simultaneously accessed by multiple threads.
#include <iostream> #include <thread> #include <vector> #include <mutex> using namespace std; mutex gLock; static int shared_value= 0; void sharedGlobalValueFunc(){ gLock.lock(); shared_value = shared_value + 1; gLock.unlock(); } int main(){ vector<thread> threads; for(int i=0; i < 1000; i++){ threads.push_back(thread(sharedGlobalValueFunc)); } for(int i=0; i < 1000; i++){ threads[i].join(); } cout << "Shared value:" << shared_value << endl; return 0; }
Output:
Shared value:1000
If I forget to unlock, then thread 2 will be waiting for thread1 to release endlessly. This will freeze the system.
#include <iostream> #include <thread> #include <vector> #include <mutex> using namespace std; mutex gLock; static int shared_value= 0; void sharedGlobalValueFunc(){ gLock.lock(); shared_value = shared_value + 1; //gLock.unlock(); } int main(){ vector<thread> threads; for(int i=0; i < 1000; i++){ threads.push_back(thread(sharedGlobalValueFunc)); } for(int i=0; i < 1000; i++){ threads[i].join(); } cout << "Shared value:" << shared_value << endl; return 0; }
Output:
shirley:~/Documents/lab_assignments/cpp/thread$ g++ -std=c++17 thread5_nounlock.cpp -o prog5_no -lpthread shirley~/Documents/lab_assignments/cpp/thread$ ./prog5 prog5 prog5_no shirley:~/Documents/lab_assignments/cpp/thread$ ./prog5_no ^C shirley:~/Documents/lab_assignments/cpp/thread$
Though we remembered to add the unlock at the end, we might fail to add in the error cases which can lead to deadlock.
#include <iostream> #include <thread> #include <vector> #include <mutex> using namespace std; mutex gLock; static int shared_value= 0; void sharedGlobalValueFunc(){ gLock.lock(); try { shared_value = shared_value + 1; throw "danger..."; } catch(...) { cout << "Shared value:" << shared_value << endl; //Forgetting to unlock return; } gLock.unlock(); } int main(){ vector<thread> threads; for(int i=0; i < 1000; i++){ threads.push_back(thread(sharedGlobalValueFunc)); } for(int i=0; i < 1000; i++){ threads[i].join(); } cout << "Shared value:" << shared_value << endl; return 0; }
shirley:~/Documents/lab_assignments/cpp/thread$ g++ -std=c++17 thread5_nounlock2.cpp -o prog5_no2 -lpthread shirley:~/Documents/lab_assignments/cpp/thread$ ./prog5_no2 Shared value:1 ^C shirley:~/Documents/lab_assignments/cpp/thread$
Just adding the unlock while returning from the function is mandatory.
#include <iostream> #include <thread> #include <vector> #include <mutex> using namespace std; mutex gLock; static int shared_value= 0; void sharedGlobalValueFunc(){ gLock.lock(); try { shared_value = shared_value + 1; throw "danger..."; } catch(...) { cout << "Shared value:" << shared_value << endl; gLock.unlock(); return; } gLock.unlock(); } int main(){ vector<thread> threads; for(int i=0; i < 1000; i++){ threads.push_back(thread(sharedGlobalValueFunc)); } for(int i=0; i < 1000; i++){ threads[i].join(); } cout << "Shared value:" << shared_value << endl; return 0; }
There are other methods which can be used in place of mutex lock.
- lock_guard
- unique_lock
- scoped_lock
- condition_variable
lock_guard
lock_guard
is a mutex wrapper that provides a convenient RAII-style mechanism for owning a mutex for the duration of a scoped block.RAII style mechanisms are focused to improve the C++ code.
This lock_guard enforces the principles of RAII which is more important to C++. It makes the program a lot more elegant and error-free.
#include <iostream> #include <thread> #include <vector> #include <mutex> using namespace std; mutex gLock; static int shared_value= 0; void sharedGlobalValueFunc(){ lock_guard<mutex> lock(gLock); //gLock.lock(); try { shared_value = shared_value + 1; throw "danger..."; } catch(...) { cout << "Shared value:" << shared_value << endl; // gLock.unlock(); return; } //gLock.unlock(); } int main(){ vector<thread> threads; for(int i=0; i < 1000; i++){ threads.push_back(thread(sharedGlobalValueFunc)); } for(int i=0; i < 1000; i++){ threads[i].join(); } cout << "Shared value:" << shared_value << endl; return 0; }
Output:
Shared value:1 Shared value:2 Shared value:3 Shared value:4 Shared value:5 Shared value:6 Shared value:7 Shared value:8 Shared value:9 Shared value:10 Shared value:11 Shared value:12 Shared value:13 Shared value:14 Shared value:15 Shared value:16 . . . . Shared value:1000
Click here for multithreading in C
References
https://www.youtube.com/watch?v=Zp17-UDKM90
https://github.com/MikeShah/moderncppconcurrency/blob/main/thread2.cpp
Comments
Post a Comment