C++ thread 互斥操作

发布时间 2023-10-04 11:20:35作者: 王清河

Thread Mutex

  • std::mutex 是 C++11 最基本的互斥量,该类的实例化对象提供了资源独占所有权的特性,用于保护共享数据免受多个线程同时访问的同步原语。

Mutex

用法

  1. 头文件
    • #include<mutex>
  2. 类型
    • std::mutex
      • 最基础的 Mutex 类
    • std::recursive_mutex
      • 递归的 Mutex 类
    • std::time_mutex
      • 定时 Mutex
    • std::recursive_time_mutex
      • 定时递归 Mutex 类
  3. 操作
    • 加锁
      • lock
    • 解锁
      • unlock
    • 查看是否上锁
      • trylock
      • 未上锁,返回 false,并锁住
      • 其他线程已上锁,返回 true
      • 同一线程已经上锁,将会产生死锁

代码

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

using namespace std;
int g_resource_printer = 4;
mutex mx;

void person_1()
{
    mx.lock();
    g_resource_printer++;
    this_thread::sleep_for(chrono::seconds(2));
    cout << "Thread_1 using printer.... g_resource_printer : " << g_resource_printer << endl;
    mx.unlock();
}

void person_2()
{
    mx.lock();
    g_resource_printer--;
    this_thread::sleep_for(chrono::seconds(2));
    cout << "Thread_2 Using printer.... g_resource_printer : " << g_resource_printer << endl;
    mx.unlock(); 
}

int main()
{
    thread thread1(person_1);
    thread thread2(person_2);

    thread1.join();
    thread2.join();

    return 0;
}

lock_gurad

介绍

  • 会在实例化时去获取一个 mutex 的所有权,在作用域消失时,会自动析构并释放 mutex
  • 创建即加锁,作用域结束自动析构并解锁,无需自动解锁
  • 不能中途解锁,也不支持手动解锁
  • 不能复制

代码

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

using namespace std;

mutex g_mtx;
int g_resource = 1;

void proc_1()
{
    lock_guard<mutex> lk(g_mtx);
    // the code for handling shared resources
    g_resource++;
    cout << "Proc 1 : " << g_resource << endl;
}

void proc_2()
{
    lock_guard<mutex> lk(g_mtx);
    g_resource--;
    cout << "Proc 2 : " << g_resource << endl;

}

int main()
{
    thread thread1(proc_1);
    thread thread2(proc_2);

    thread1.join();
    thread2.join();

    return 0;
}

unique_lock

介绍

  • 创建时可以不锁定,在需要的再锁定
  • 可以手动的,随时加锁解锁
  • 作用域结束后,析构函数自动释放锁
  • 不可复制,可移动
  • 条件变量需要该类型的锁作为参数(此时必须使用 unique_lock)

代码

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

struct Box
{
    explicit Box(int num) : num_thing(num){}
    int num_thing;
    std::mutex m;
};

void transfer(Box& from, Box &to, int num)
{
    // std::defer_lock means locks later
    std::unique_lock<std::mutex> lock1(from.m, std::defer_lock);
    std::unique_lock<std::mutex> lock2(to.m, std::defer_lock);
	
    std::lock(lock1, lock2);	// now lock lock1 and lock2
	// lock1.lock()
	// lock2.lock()

    from.num_thing -= num;
    to.num_thing += num;
}

int main()
{
    Box acc1(100);
    Box acc2(200);

    std::thread thread1(transfer, std::ref(acc1), std::ref(acc2), 10);
    std::thread thread2(transfer, std::ref(acc1), std::ref(acc2), 20);
    thread1.join();
    thread2.join();

    std::cout << "acc1.num_thing : " << acc1.num_thing << std::endl;
    std::cout << "acc2.num_thing : " << acc2.num_thing << std::endl;

    return 0;
}

总结:

  1. 为了解决多线程资源竞争,C++ 使用 mutex 类提供数据保护
  2. 为了践行RAII思想,或者说为了防止加锁后忘记解锁,使用 lock_guard
  3. 为了更方便的使用锁,提供 unique_lock 即保证了 RAII,又可以手动解锁