C++ multithreading: definition and use of mutual exclusion locks, spin locks, condition variables, and read-write locks

Mutex, spin lock, condition variable

Mutex locks use std::mutexclasses; condition variables use std::condition_variableclasses; spin locks are implemented by C++11 std::atomicclasses and use "spin" CAS operations.
Spin lock reference: C++11 implements spin lock

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

// 使用C++11的原子操作实现自旋锁(默认内存序,memory_order_seq_cst)
class spin_mutex {
    
    
    // flag对象所封装的bool值为false时,说明自旋锁未被线程占有。  
    std::atomic<bool> flag = ATOMIC_VAR_INIT(false);       
public:
    spin_mutex() = default;
    spin_mutex(const spin_mutex&) = delete;
    spin_mutex& operator= (const spin_mutex&) = delete;
    void lock() {
    
    
        bool expected = false;
        // CAS原子操作。判断flag对象封装的bool值是否为期望值(false),若为bool值为false,与期望值相等,说明自旋锁空闲。
        // 此时,flag对象封装的bool值写入true,CAS操作成功,结束循环,即上锁成功。
        // 若bool值为为true,与期望值不相等,说明自旋锁被其它线程占据,即CAS操作不成功。然后,由于while循环一直重试,直到CAS操作成功为止。
        while(!flag.compare_exchange_strong(expected, true)){
    
     
            expected = false;
        }      
    }
    void unlock() {
    
    
        flag.store(false);
    }
};

int k = 2;
// 实现互斥
spin_mutex smtx; // 自旋锁,如果自旋锁已经被占用,调用者就一直循环检查自旋锁是否被解除占用。
mutex mtx; // 互斥锁,如果互斥锁已经被占用,调用者这会进入睡眠状态,等待互斥锁解除占用时被唤醒。
// 实现同步
condition_variable cond;

// 不加锁
void print_without_mutex(int n){
    
    
    for(int i = 1; i <= n ; i++){
    
    
        cout << this_thread::get_id() << ": " << i << endl;
    }
}
// 使用互斥锁,实现互斥
void print_with_mutex(int n){
    
    
    unique_lock<mutex> lock(mtx);
    for(int i = 1; i <= n ; i++){
    
    
        cout << this_thread::get_id() << ": " << i << endl;
    }
}
// 使用自旋锁,实现互斥
void print_with_spin_mutex(int n){
    
    
    smtx.lock();
    for(int i = 1; i <= n ; i++){
    
    
        cout << this_thread::get_id() << ": " << i << endl; 
    }
    smtx.unlock();
}
// 使用条件变量,实现同步(需要与mutex配合使用),打印 1,1,2,2,3,3...
void print_with_condition_variable(int n){
    
    
    unique_lock<mutex> lock(mtx);
    for(int i = 1; i <= n ; i++){
    
    
        while(!(i <= k/2)){
    
      // 循环检查某个条件(i <= k/2)是否满足,满足则跳出循环,继续向下执行。
            cond.wait(lock); // 阻塞当前线程,等待lock对象封装的互斥锁mtx解除占用(收到解除占用互斥锁的线程的notify)
        }
        cout << this_thread::get_id() << ": " << i << endl; 
        k++;
        cond.notify_one(); // 随机唤醒一个等待的线程
    }
}

int main(){
    
    
    thread t1(print_with_spin_mutex,10);
    thread t2(print_with_spin_mutex,10);
    t1.join();
    t2.join();
    return 0;
}

Read-write lock

C++17 is provided shared_mutexto solve the reader-writer problem, which is read-write lock. Unlike ordinary locks, read-write locks can only have one writer or multiple readers at the same time, but cannot have both readers and writers at the same time. The performance of read-write locks is generally better than that of ordinary locks.

shared_mutexThan the average mutexmore function lock_shared() / unlock_shared(), allowing multiple (readers) threads simultaneously lock, unlock, and shared_lockis equivalent to the shared version lock_guard. To shared_mutexuse lock_guardor unique_lockto achieve the purpose writers exclusive.

The following code comes from: C++ concurrent programming (7): Read-Write Lock

#include <thread>
#include <shared_mutex>
#include <iostream>
#include <vector>
#include <unistd.h> // sleep(seconds), usleep(microseconds)
using namespace std;


class Counter {
    
    
public:
  Counter() : value_(0) {
    
    
  }

  // Multiple threads/readers can read the counter's value at the same time.
  std::size_t Get() const {
    
    
    std::shared_lock<std::shared_mutex> lock(mutex_);
    return value_;
  }

  // Only one thread/writer can increment/write the counter's value.
  void Increase() {
    
    
    // You can also use lock_guard here.
    std::unique_lock<std::shared_mutex> lock(mutex_);
    value_++;
  }

  // Only one thread/writer can reset/write the counter's value.
  void Reset() {
    
    
    std::unique_lock<std::shared_mutex> lock(mutex_);
    value_ = 0;
  }

private:
  mutable std::shared_mutex mutex_;
  std::size_t value_;
};


std::mutex g_io_mutex;

void Worker(Counter& counter) {
    
    
  for (int i = 0; i < 3; ++i) {
    
    
    counter.Increase();
    std::size_t value = counter.Get();

    std::lock_guard<std::mutex> lock(g_io_mutex);
    std::cout << std::this_thread::get_id() << ' ' << value << std::endl;
  }
}

int main() {
    
    
  const std::size_t SIZE = 2;

  Counter counter;

  std::vector<std::thread> v;
  v.reserve(SIZE);

  v.emplace_back(Worker, std::ref(counter));
  v.emplace_back(Worker, std::ref(counter));

  for (std::thread& t : v) {
    
    
    t.join();
  }

  return 0;
}

Output result:
140561343293184 1
140561343293184 3
140561343293184 4
140561334900480 2
140561334900480 5
140561334900480 6

Guess you like

Origin blog.csdn.net/XindaBlack/article/details/105915806