Tanmay Kachroo

Pessimistic Locking using Mutex (in C++)


Pessimistic Locking

Pessimistic locking is a concurrency control mechanism used to ensure that only one thread or process can access a shared resource at a time which helps to avoid issues like race conditions, data corruption, or inconsistent states.

When a thread wants to access a shared resource, it acquires a lock on that resource before performing any operations. This lock ensures that other threads or processes are prevented from accessing or modifying the resource until the lock is released.

Ideally, we would want to lock minimum operations inside the lock such that parallelism is maximized.

Example

The following code demonstrates a simple multithreaded program where two threads, t1 and t2, concurrently increment a shared variable count.

The output of the program will vary on each run due to the race condition. The final value of count will depend on the interleaving of the thread executions, and therefore, it may not be consistent or predictable.

#include <iostream>
#include <thread>
using namespace std;

int count = 0;

void increment() {
  for (int i = 0; i < 100000; ++i) {
    count++;
  }
}

int main() {
  thread t1(increment);
  thread t2(increment);

  t1.join();
  t2.join();
  cout << count;
}

// Output: 124838
// Output: 114711
// Output: 128148
// ...

The program gives different output everytime we execute it. The expected output is 200000

Using mutex locks

In the increment function, each thread executes a loop where the mutex mtx is locked before incrementing the count variable, and then unlocked afterward. This ensures that only one thread can modify count at a time, preventing race conditions and ensuring correct results.

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

int count = 0;

mutex mtx;

void increment() {
  for (int i = 0; i < 100000; ++i) {
    mtx.lock();   // count is locked
    count++;
    mtx.unlock();   // count is unlocked
  }
}

int main() {
  thread t1(increment);
  thread t2(increment);

  t1.join();
  t2.join();
  cout << count;
}

// Output: 200000
// Output: 200000
// Output: 200000
// ...

The program gives the expected output, 200000 every time we execute it.


Read more: