C++多线程:mutex

互斥量

C++11互斥锁定义在<mutex>头文件中,提供了独占资源的特性

C++11头文件中定义的互斥量

互斥量 说明
mutex 基本互斥量
recursive_mutex 递归互斥量
timed_mutex 定时互斥量
recursive_timed_mutex 递归定时互斥量

std::mutex

最基本的互斥量,提供了独占所有权的特性
默认初始化的mutex对象是unlocked
不允许拷贝构造和移动构造

std::mutex成员函数

  • lock()
    若互斥量未被锁住,则当前线程将互斥量加锁,直到调用unlock()释放锁
    若互斥量被其他线程锁住,则当前线程被阻塞
    若互斥量已被当前线程拥有,则产生死锁
  • try_lock()
    若互斥量未被锁住,则当前线程将互斥量加锁,直到调用unlock()释放锁
    若互斥量被其他线程锁住,则当前线程返回false,而不会被阻塞
    若互斥量已被当前线程拥有,则产生死锁
  • unlock()
    释放互斥量的所有权

如下示例,给变量count加锁

std::mutex mtx;
if (mtx.try_lock()) {
    ++count;
    mtx.unlock();
}

std::recursive_mutex

递归互斥量,允许一个线程对互斥量多次加锁,即递归加锁,来获取对互斥量对象的多层所有权
在释放递归互斥量时,需要保证与加锁次数相同,其他特性与std::mutex大致相同

std::timed_mutex

定时互斥量,比std::mutex多两个成员函数try_lock_for()try_lock_until()

  • try_lock_for()
    接收一个时间范围实参,若线程在指定时间段内没有获得锁,则被阻塞,并返回false
    while(!tmtx.try_lock_for(std::chrono::milliseconds(200))){
    }
    
  • try_lock_until()
    接收一个时间点实参,若线程在指定时间点没有获得锁,则被阻塞,并返回false

std::recursive_timed_mutex

定时递归互斥量

C++11提供RAII特性优化互斥量

与锁相关的Tag类

tag标记类,配合lock_guardunique_lock使用,以标记锁类型
与STL中区分迭代器种类的设计类似,用于函数重载时提供参数类型支持

C++11提供了如下类型

  • std::adopt_lock_t
    空标记类,定义了常量对象constexpr adopt_lock_t adopt_lock{};
  • std::defer_lock_t
    空标记类,定义了常量对象constexpr defer_lock_t defer_lock{};
  • std::try_to_lock_t
    空标记类,定义了常量对象constexpr try_to_lock_t try_to_lock{};

lock_guard

C++11中定义的模板类,与RAII相关,方便对互斥量加/释放锁,简化编写与mutex相关的异常处理代码
lock_guard对象并不负责管理Mutex对象的生命周期,只是简化了Mutex对象的上锁和解锁操作
类定义

template<typename Mutex>
class lock_guard;

构造函数

  • locking
    explicit lock_guard(mutex_type&);
    
    在构造时加锁
  • adopting
    lock_guard(mutex_type&, adopt_lock_t tag);
    
    与locking不同的是,互斥量已被当前线程锁住,此后锁对象交由lock_guard对象管理
    mtx.lock();
    std::lock_guard<std::mutex> lck(mtx, std::adopt_lock);
    

拷贝构造

lock_guard(const lock_guard&)=delete;

不支持拷贝构造或移动构造

unique_lock

unique_lock对象同样也不负责管理 Mutex 对象的生命周期
与RAII相关的模板类,相比于lock_guard提供成员函数对锁进行操作,从而灵活度更强
一个unique_lock对象只能和一个mutex所对应,否则可能出现死锁

构造函数

  • default

    unique_lock()noexcept;
    
  • locking

    explicit unique_lock(mutex_type&);
    

    传入mutex对象,并尝试调用lock()加锁

  • try_locking

    unique_lock(mutex_type&, try_to_lock_t tag);
    

    传入mutex对象,并尝试调用try_lock()加锁

    std::unique_lock mul(mlock, std::defer_lock);
    if(mul.try_lock()==true){
        s+= i;
    }
    
  • deferred

    unique_lock(mutex_type&, defer_lock_t)noexcept;
    

    传入mutex对象,并不对互斥量加锁,需要使用lock()/unlock手动加/释放锁

    void work2(int& s){
        for(int i=5001; i<=10000; i++>){
            std::unique_lock<std::mutex> mul(mlock, std::defer_lock);
            mul.lock();
            s+= i;
            mul.unlock(); // 可以不用unlock,unique_lock析构时会自动unlock
        }
    }
    
  • adopting

    unique_lock(mutex_type&, adopt_lock_t);
    

    传入一个已经被当前线程加锁的mutex对象,表示互斥量已加锁,不需要再重复加锁
    该互斥量之前必须已经加锁,才可以使用该参数

  • locking_for

    template<class Rep,class Period>
    unique_lock(mutex_type&, const chrono::duration<Rep,Period>&)
    

    传入mutex对象,并尝试调用try_lock_for()加锁

  • locking_until

    template<class Clock, class Duration>
    unique_lock(mutex_type&,const chrono::time_point<Clock,Duration>&)
    

    传入mutex对象,并尝试调用try_lock_until()加锁

拷贝构造函数

unique_lock(const unique_lock&)=delete;

被禁用,不可拷贝

移动构造函数

unique_lock(unique_lock&& x);

移动构造,转移对mutex对象的所有权,调用成功后,x不再管理任何mutex对象

赋值操作

  • 赋值操作
    unique_lock& operator=(const unique_lock&)=delete;
    
    被禁用,不可拷贝
  • 移动赋值
    unique_lock& operator=(unique_lock&& x)noexcept;
    
    转移对mutex对象的所有权,调用成功后,x不再管理任何mutex对象
std::unique_lock<std::mutex> rtn_ul(){
    std::unique_lock<std::mutex> tmp(mlock);
    return tmp;
}
void work(int& s){
    for(int i=1; i<=5000; i++){
        std::unique_lock<std::mutex> mul2= rtn_ul();
        s+= i;
    }
}

成员函数

lock()
try_lock()
try_lock_for()
try_lock_until()
unlock()

swap()在unique_lock对象之间转移锁资源

owns_lock()返回当前unique_lock对象是否管理锁
operator bool()owns_lock()功能类似

mutex()返回当前unique_lock对象所管理的mutex对象

std::release()
解除unique_lock和mutex的联系,返回原mutex对象指针,若之前的mutex已经加锁,则后面需要自己手动释放锁

std::unique_lock<std::mutex> mul(mlock);
std::mutex* m= mul.release();
s+= i;
m->unlock();

示例如下:

#include<iostream>
#include<mutex>
std::mutex mlock;
void work1(int& s){
    for(int i=1; i<=5000; i++>){
        std::unique_lock<std::mutex> mul(mlock, std::try_to_lock);
        if(mul.owns_lock()==true){
            s+= i;
        }else{
            // 执行没有共享内存的代码
        }
    }
}
void work2(int& s){
    for(int i=5001; i<=10000; i++>){
        std::unique_lock<std::mutex> mul(mlock, std::try_to_lock);
        if(mul.owns_lock()==true){
            s+= i;
        }else{
            // 执行没有共享内存的代码
        }
    }
}
int main(){
    int ans= 0;
    std::thread t1(work1, std::ref(ans));
    std::thread t2(work2, std::ref(ans));
    t1.join();
    t2.join();
    std::cout<< ans<<std::endl;

    return 0;
}

<mutex>头文件其他函数

提供的公共函数

  • std::lock()
    可以同时对多个互斥量加锁
  • std::try_lock()
    尝试同时对多个互斥量加锁
  • std::call_once()
    若多个线程需要同时调用某个函数,call_once()可以保证多个线程对该函数只调用一次
posted @ 2024-11-01 20:06  sgqmax  阅读(19)  评论(0编辑  收藏  举报