Threads in C++ & Concurrency

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. 

  1. lock_guard
  2. unique_lock 
  3. scoped_lock
  4. condition_variable

lock_guard

The class 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 

modernescpp

https://www.youtube.com/watch?v=Zp17-UDKM90

https://github.com/MikeShah/moderncppconcurrency/blob/main/thread2.cpp


Comments