Mutex / Mutual Exclusion / Locking mechanism / Block / Sleep

if 1 thread is in CS other cannot enter, Return value: None, Parameters: None
How Mutex is internally implemented?
  Mutex is kernel maintained lock(a data structure) that we set before using a shared resource and release after using it.
  Mutex keeps track of who currently has exclusive access to the data.
  When the lock is set, no other thread can access the locked region of code.
  Mutex lock will only be released by the thread who locked it.
Wake up?
  When call to mutex.unlock() comes, signal is sent to scheduler. Scheduler checks all threads waiting on mutex.
if 1000 threads are waiting, wakeup call to activate 1000 comes(also called thundering herd), but scheduler wakes up 1 thread(at its discretion) & 999 falls to sleep.

Problems with Mutex

Problem Description
Priority Inversion Lower priority process is executing in Critical section, suddenly High-Priority process is scheduled, lower-priority process is preempted & thrown out of CS & higher priority process excecutes in CS. Also if Higher priority thread/process is Busy Waiting then lower priority process will never get CPU(ie never scheduled).
Can PI happen on user-level threads? No, there is no preemption in user level threads.
Easy Deadlock if order of mutex locking/unlocking is not correct, that can led to easy dead-lock situation. See Dead-lock example.
Thread holding mutex paniced if thread-1 which holding the lock panics, whole process would panic.
Mutex and data are seperate Entities Thread-1,2 are accessing data using mutex, But thread-3 changed the data without mutex, this should not Happen.
Solutions:
1. Making mutex and data as single entity as done in Rust
2. All times keeping in mind that data should not handled outside mutex guards

#include <iostream>
#include <thread>
#include <mutex>

std::mutex m;
int a = 1;
void test(int tid) {
    m.lock();
    a++;
    std::cout << "Thread-" << tid << ", a=" << a << "\n";
    m.unlock();
}
int main() {
    std::thread t1(test,1);
    a += 10;                                //But Thread-3=Main changed data without mutex.
    std::thread t2(test,2);                 //Thread-1,2 will access data using mutex.
    t1.join();
    t2.join();
    return 0;
}
$ ./a.out
Thread-1: 5
Thread-2: Random value

Creating Mutex

C++11 POSIX Rust

int var = 0;
mutex m;
void fun(int tid) {
    m.lock();
    cout << "tid: " << tid << ", var: " <<  var++ << "\n";
    m.unlock();
}
int main() {
    int a = 1;
    int num_of_thrs = 5;
    thread t[num_of_thrs];
    for (auto i = 0; i < num_of_thrs; i++)
    {
        t[i] = thread(fun, i);
    }

    for (auto i = 0; i < num_of_thrs; i++)
        t[i].join();
    return 0;
}
/*
Without mutex:  //2 threads enter function at same time
tid: 1, var: 0
tid: 3, var: 1
tid: 4, var: 2
tid: tid: 2, var: 3
0, var: 4

With mutex:
tid: 0, var: 0
tid: 3, var: 1
tid: 4, var: 2
tid: 1, var: 3
tid: 2, var: 4
*/
          
       
#include <pthread.h>
#include <stdio.h>

int counter;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

void *fun() {
    //Thread-2 sleeps until Thread-1 unlocks the mutex
    pthread_mutex_lock(&lock);
    printf("Inside CS\n");
    pthread_mutex_unlock(&lock);
}

int main(){
    pthread_t tid1,tid2;  //Defined as int
    pthread_create(&tid1,NULL,&fun,NULL);
    pthread_create(&tid2,NULL,&fun,NULL);

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
}
In Rust, data and mutex are not seperate. ie data can be accessed inside mutex only.
This solves the problem which we is present in CPP
use std::sync::Mutex;
fn main() {
    // Create mutex and associate a i32 data with it(whose initial value=5).
    let mtx = Mutex::new(5);
    {
        //After acquiring lock, data inside mutex can be changed
        let mut n = mtx.lock().unwrap();     
        *n = 6;
    }
    {
        // Print after acquiring the lock
        let n = mtx.lock().unwrap();
        println!("{}", n);
    }    
}

Wrappers around mutex

These are classes which owns mutex and provide RAII

lock_guard (smaller=faster) unique_lock (heavy wrt lock_guard)
What We donot need to unlock this mutex,
When lock_guard object goes out of scope, mutex is automatically unlocked
Feature rich version of lock_guard
Features Not copy construtible. Move Assignable.
Because operator = is deleted.
Move Assignable(yes), copy construtible(no)
Properties
lock_guard locks only when it is created.
unique_lock has more features:
1. Lock/unlock again after 1st lock.
2. unique_lock be moved to other object
3. unique_lock uses a little more memory to track if it is locked.
4. deferred locking: Acquire the mutex but donot lock immediately
5. time-constrained attempts at locking: try_lock_for(), try_lock_until()
6. recursive locking
Code
int var = 0;
mutex m;

void fun(int tid) {
  // unlike mutex, lg.unlock() is not needed
  lock_guard lg(m);
  cout << "tid: " << tid << ", var: " <<  var++ << "\n";
}
int main() {
    int a = 1;
    int num_of_thrs = 5;
    thread t[10];
    for (auto i = 0; i < num_of_thrs; i++) {
        t[i] = thread(fun, i);
    }

    for (auto i = 0; i < num_of_thrs; i++)
        t[i].join();
    return 0;
}
/*
Without lock_guard:
tid: thr_id: 0, var: 0
tid: 1, var: 1
tid: 34, var: 2
, var: tid: 2, var: 3
4

With lock_guard:
tid: 0, var: 0
tid: 1, var: 1
tid: 2, var: 2
tid: 4, var: 3
tid: 3, var: 4
*/
Immediate lock
mutex m;
void fun(int tid) {
    unique_lock ul(m);
    // Do some work here
    ul.unlock();    // unlock early
    // Do other work without the lock
    ul.lock();      // lock again
}


unique_lock <defer_lock>
- Donot lock immediately. Most of deadlock problem occur because of immediate mutex locking.
- defer_lock delays locking on mutex for some time. defer_lock example(Deadlock)
std::mutex mtx;
void task() {
    // Construct the unique_lock, donot lock mutex immediately
    std::unique_lock lock(mtx, std::defer_lock);

    // Do some work while the mutex is NOT locked...

    // Now, manually lock the mutex when you actually need it
    lock.lock();
    // Critical section
} // The destructor of 'lock' will automatically unlock mtx when it goes out of scope

int main() {
    std::thread t(task);
    t.join();
    return 0;
}

1 Writer, Multiple Readers

#include <shared_mutex>
std::shared_mutex mtx;

// For read-only operations (e.g., getCurrentCount)
shared_lock <std::shared_mutex> rlock(mtx); // multiple readers allowed

// For write operations (e.g., allowRequest when it modifies state)
unique_lock <shared_mutex> wlock(mtx); // single writer