C++ 锁

    C++提供了两种常用的锁,std::lock_guard<Lockable &T>和std::unique_lock<Lockable &T>。通常使用场景下,这两个锁用法一致。即,在构造锁对象时上锁,在析构锁对象时解锁。使用户从上锁/解锁操作中解放出来,有效地避免死锁。

    1,考虑如下一般场景:两个线程访问同一个共享资源,使用lock_guard或者unique_lock都可以实现资源访问互斥。

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

int var = 0;
std::mutex gMutex;

void threadFun(int a) {
    while (true) {
        std::lock_guard<std::mutex> lock(gMutex);    // 构造时上锁

        // std::unique_lock<std::mutex> lock(gMutex);     // 使用 unique_lock 也能达到同样的效果

        var = a;
        std::cout << "var=" << var << std::endl;
    }    // 析构时解锁
}
int main() {
    std::thread th1(threadFun, 1);
    std::thread th2(threadFun, 2);

    th1.join();
    th2.join();

    return 0;
}


    2,std::lock(Lockable &lock1, Lockable &lock2, Lockable &... lockn)

        考虑到一个线程可能需要获取多个锁,为了避免类似哲学家就餐死锁的问题,C++11提供了std::lock()函数,该函数同时锁定多个锁,从而能够有效地避免死锁问题。注意:

    (1)该函数要求调用者至少提供两个锁。

    (2)标准库只提供了std::lock()上锁方法,没有提供对应的解锁函数。该函数主要目的是配合 std::lock_guard() 和 std::unique_lock() ,由 std::lock_guard() 和 std::unique_lock() 来自动解锁。详细解释见下面几节。

 

    3,std::lock_guard

        C++11 提供的std::lock_guard比较简单。构造函数有两个:

    (1)lock_guard( mutex_type& m ):该构造函数需要提供一个mutex m。构造时,即上锁。如果m不是递归锁,并且在构造前,当前线程已经拥有该互斥锁,则行为是未定义的。

    (2)lock_guard( mutex_type& m, std::adopt_lock_t t ):该构造函数需要提供一个mutex m和一个锁定策略。

            锁定策略有如下三种:

            defer_lock:std::defer_lock_t类型,不要求当前线程拥有mutex

            try_to_lock:std::try_to_lock_t类型,非阻塞式获取mutex

            adopt_lock:std::adopt_lock_t类型,假定当前线程已经拥有mutex

            所以,在使用这个构造函数前,需要先把mutex上锁。

        示例如下:

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

int var_a = 0;
int var_b = 0;
std::mutex mutex_a;
std::mutex mutex_b;

void threadFun(int a, int b) {
    while (true) {
        std::lock(mutex_a, mutex_b);    // 先上锁,同时锁定两个mutex
        std::lock_guard<std::mutex> lock_a(mutex_a, std::adopt_lock);    // std::adopt_lock表明当前线程已经给mutex_a上锁,退出时解锁
        std::lock_guard<std::mutex> lock_b(mutex_b, std::adopt_lock);    //std::adopt_lock表明当前线程已经给mutex_b上锁,退出时解锁

        var_a = a;
        var_b = b;
        std::cout << "a=" << var_a << std::endl;
        std::cout << "b=" << var_b << std::endl << std::endl;

    }
}

int main() {
    std::thread th1(threadFun, 1, 1);
    std::thread th2(threadFun, 100, 100);

    th1.join();
    th2.join();

    return 0;
}
    上述示例展示了 std::lock_guard和std::lock配合锁定和解锁多个mutex。std::lock负责同时锁定多个锁,std::lock_guard负责解锁。

    4,std::unique_lock

        之前提到过,unique_lock在通常情况下跟lock_guard没有不同。lock_guard很简单,只有两个构造函数。而unique_lock却要复杂得多,unique_lock提供了很多种构造函数,用来进行更加精细和细粒度的控制。无论从内存还是运行效率上来讲,unique_lock都要比lock_guard开销稍大。unique_lock构造函数如下:

    (1)unique_lock():默认构造函数。构造一个无mutex关联的unique_lock。析构不会解锁。

    (2)unique_lock( unique_lock&& other ):移动构造函数。以other的内容初始化当前unique_lock,使得other无mutex关联。

    (3)unique_lock( mutex_type& m ):关联mutex,内部锁定mutex m,析构解锁。如果mutex m非递归,且已经被当前线程占用,则行为是未定义的。

    (4)unique_lock( mutex_type& m, std::defer_lock_t t ):关联mutex m,内部不锁定mutex m,析构不解锁。

    (5)unique_lock( mutex_type& m, std::try_to_lock_t t ):关联mutex m,非阻塞地尝试锁定mutex m,析构解锁。如果mutex m非递归,且已经被当前线程占用,则行为是未定义的。

    (6)unique_lock( mutex_type& m, std::adopt_lock_t t ):关联mutex m,内部不锁定mutex m,但是假定当前线程已经锁定mutex,析构解锁。

        重点关注(4)和(6)。这两个构造函数都关联mutex m,但内部都不会调用m.lock()去锁定。唯一的区别是,adopt_lock会假定当前线程已经锁定mutex m,因此其析构函数会调用m.unlock()去解锁;而defer_lock会假定当前线程也没有锁定mutex m,因此其析构函数不会调用m.unlock()去解析。示例如下:

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

int var_a = 0;
int var_b = 0;
std::mutex mutex_a;
std::mutex mutex_b;

void threadFun(int a, int b) {
    while (true) {
        std::lock(mutex_a, mutex_b);
        std::unique_lock<std::mutex> lock_a(mutex_a, std::defer_lock);    // defer_lock 构造时不锁定,析构时不解锁,循环会死锁。
        std::unique_lock<std::mutex> lock_b(mutex_b, std::defer_lock);    // 同上

//        std::unique_lock<std::mutex> lock_a(mutex_a, std::adopt_lock);    // adopt_lock构造时不锁定,析构时解锁,循环正常。
//        std::unique_lock<std::mutex> lock_b(mutex_b, std::adopt_lock);    // 同上

        var_a = a;
        var_b = b;
        std::cout << "a=" << var_a << std::endl;
        std::cout << "b=" << var_b << std::endl << std::endl;

    }
}

int main() {
    std::thread th1(threadFun, 1, 1);
    std::thread th2(threadFun, 100, 100);

    th1.join();
    th2.join();

    return 0;
}

转载于:https://my.oschina.net/yepanl/blog/2200618

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值