[C++11 并发编程] 08 - Mutex std::unique_lock

相对于std::lock_guard来说,std::unique_lock更加灵活,std::unique_lock不拥有与其关联的mutex。构造函数的第二个参数可以指定为std::defer_lock,这样表示在构造unique_lock时,传入的mutex保持unlock状态。然后通过调用std::unique_lock对象的lock()方法或者将将std::unique_lock对象传入std::lock()方法来锁定mutex。

#include <mutex>

class some_big_object
{};

void swap(some_big_object& lhs,some_big_object& rhs)
{}

class X
{
private:
    some_big_object some_detail;
    mutable std::mutex m;
public:
    X(some_big_object const& sd):some_detail(sd){}

    friend void swap(X& lhs, X& rhs)
    {
        if(&lhs==&rhs)
            return;
        // 构造unique_lock,保持mutex为unlocked状态
        std::unique_lock<std::mutex> lock_a(lhs.m,std::defer_lock);
        std::unique_lock<std::mutex> lock_b(rhs.m,std::defer_lock);
        // lock mutex
        std::lock(lock_a,lock_b);
        swap(lhs.some_detail,rhs.some_detail);
    }
};

int main()
{}

std::unique_lock比std::lock_guard需要更大的空间,因为它需要存储它所关联的mutex是否被锁定,如果被锁定,在析构该std::unique_lock时,就需要unlock它所关联的mutex。std::unique_lock的性能也比std::lock_guard稍差,因为在lock或unlock mutex时,还需要更新mutex是否锁定的标志。大多数情况下,推荐使用std::lock_guard但是如果需要更多的灵活性,比如上面这个例子,或者需要在代码之间传递lock的所有权,这可以使用std::unique_lock。

std::unique_lock并不拥有与其关联的mutex,mutex的所有权可以在不同的实例之间进行传递。比如我们期望提供一个函数,在锁定mutex后,将mutex的所有权返回给调用者,这样可以让调用者在mutex的保护下,执行更多额外的操作。如下所示的get_lock()函数,锁定mutex后,执行prepare_data(),然后将mutex返回给调用者,调用者将mutex传入其自己的局部变量std::unique_lock,然后mutex的保护下调用do_something()。当process_data()退出时,会自动unlock这个mutex。

std::unique_lock<std::mutex> get_lock()
{
    extern std::mutex some_mutex;
    std::unique_lock<std::mutex> lk(some_mutex);
    prepare_data();
    return lk;
}
void process_data()
{
    std::unique_lock<std::mutex> lk(get_lock());
    do_something();
}
std::unique_lock的灵活性还在于我们可以主动的调用unlock()方法来释放mutex,因为锁的时间越长,越会影响程序的性能,在一些特殊情况下,提前释放mutex可以提高程序执行的效率。

此外,我们还需要注意锁的粒度,如果有多个线程等待相同的资源,而某个线程长时间的持有mutex,就会增加其它线程的等待时间。我们要尽量保证只对共享数据加锁,在锁定范围之外对数据进行处理。不要在锁定mutex的情况下执行I/O操作,因为I/O操作是很慢的。可以在必要的情况下调用std::unique_lock的unlock()操作来释放mutex,在需要时,再调用lock()来锁定mutex。

void get_and_process_data()
{
    std::unique_lock<std::mutex> my_lock(the_mutex);
    some_class data_to_process=get_next_data_chunk();
    my_lock.unlock(); // 在process中不需要锁定mutex
    result_type result=process(data_to_process);
    my_lock.lock(); // 在写操作前再次锁定mutex
    write_result(data_to_process,result);
} 

在swap实例中,我们锁定了两个对象的mutex再进行比较交换操作。假设我们要比较两个对象,而复制对象的代价很小,则可以考虑减小mutex保护的范围和时间,在锁定mutex时,对对象进行复制,释放mutex后,用两个复制的对象来进行比较操作。

#include <mutex>
class Y
{
private:
    int some_detail;
    mutable std::mutex m;

    // 返回对象的拷贝
    int get_detail() const
    {
        // 保护对象
        std::lock_guard<std::mutex> lock_a(m);
        return some_detail;
    }
public:
    Y(int sd):some_detail(sd){}

    friend bool operator==(Y const& lhs, Y const& rhs)
    {
        if(&lhs==&rhs)
            return true;
        // 获取要比较对象的拷贝
        int const lhs_value=lhs.get_detail();
        int const rhs_value=rhs.get_detail();
        // 比较对象的拷贝
        return lhs_value==rhs_value;
    }
};

int main()
{}

采用这种方法,虽然减小了mutex锁定的范围和时间,但是却改变了比较的语义。因为这种实现方法只能保证某一时刻读取的lhs.some_detail和另一时刻读取的rhs.some_detail相等,但是在读取lhs_value和读取lrh_value之间存在race condition,它们的值可能已经被修改了。




  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值