lock_guard:更加灵活的锁管理类模板,构造时是否加锁是可选的,在对象析构时如果持有锁会自动释放锁,所有权可以转移。对象生命期内允许手动加锁和释放锁。

scope_lock:严格基于作用域(scope-based)的锁管理类模板,构造时是否加锁是可选的(不加锁时假定当前线程已经获得锁的所有权),析构时自动释放锁,所有权不可转移,对象生存期内不允许手动加锁和释放锁。

share_lock:用于管理可转移和共享所有权的互斥对象。

使用std::lock_guard类模板修改前面的代码,在lck对象构造时加锁,析构时自动释放锁,即使insert抛出了异常lck对象也会被正确的析构,所以也就不会发生互斥对象没有释放锁而导致死锁的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
std::set<int> int_set;
std::mutex mt;
auto f = [&int_set, &mt]() {
    try {
        std::random_device rd;
        std::mt19937 gen(rd());
        std::uniform_int_distribution<> dis(1, 1000);
        for(std::size_t i = 0; i != 100000; ++i) {
            std::lock_guard<std::mutex> lck(mt);
            int_set.insert(dis(gen));
        }
    } catch(...) {}
};
std::thread td1(f), td2(f);
td1.join();
td2.join();

互斥对象管理类模板的加锁策略

前面提到std::lock_guard、std::unique_lock和std::shared_lock类模板在构造时是否加锁是可选的,C++11提供了3种加锁策略。

策略 tag type 描述
(默认) 请求锁,阻塞当前线程直到成功获得锁。
std::defer_lock std::defer_lock_t 不请求锁。
std::try_to_lock std::try_to_lock_t 尝试请求锁,但不阻塞线程,锁不可用时也会立即返回。
std::adopt_lock std::adopt_lock_t 假定当前线程已经获得互斥对象的所有权,所以不再请求锁。

下表列出了互斥对象管理类模板对各策略的支持情况。

策略 std::lock_guard std::unique_lock std::shared_lock
(默认) √(共享)
std::defer_lock ×
std::try_to_lock ×
std::adopt_lock

下面的代码中std::unique_lock指定了std::defer_lock。

1
2
3
4
5
std::mutex mt;
std::unique_lock<std::mutex> lck(mt, std::defer_lock);
assert(lck.owns_lock() == false);
lck.lock();
assert(lck.owns_lock() == true);

对多个互斥对象加锁

在某些情况下我们可能需要对多个互斥对象进行加锁,考虑下面的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
std::mutex mt1, mt2;
// thread 1
{
    std::lock_guard<std::mutex> lck1(mt1);
    std::lock_guard<std::mutex> lck2(mt2);
    // do something
}
// thread 2
{
    std::lock_guard<std::mutex> lck2(mt2);
    std::lock_guard<std::mutex> lck1(mt1);
    // do something
}

如果线程1执行到第5行的时候恰好线程2执行到第11行。那么就会出现

  • 线程1持有mt1并等待mt2
  • 线程2持有mt2并等待mt1

发生死锁。 为了避免发生这类死锁,对于任意两个互斥对象,在多个线程中进行加锁时应保证其先后顺序是一致。前面的代码应修改成

1
2
3
4
5
6
7
8
9
10
11
12
13
std::mutex mt1, mt2;
// thread 1
{
    std::lock_guard<std::mutex> lck1(mt1);
    std::lock_guard<std::mutex> lck2(mt2);
    // do something
}
// thread 2
{
    std::lock_guard<std::mutex> lck1(mt1);
    std::lock_guard<std::mutex> lck2(mt2);
    // do something
}

更好的做法是使用标准库中的std::lockstd::try_lock函数来对多个Lockable对象加锁。std::lock(或std::try_lock)会使用一种避免死锁的算法对多个待加锁对象进行lock操作(std::try_lock进行try_lock操作),当待加锁的对象中有不可用对象时std::lock会阻塞当前线程知道所有对象都可用(std::try_lock不会阻塞线程当有对象不可用时会释放已经加锁的其他对象并立即返回)。使用std::lock改写前面的代码,这里刻意让第6行和第13行的参数顺序不同

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
std::mutex mt1, mt2;
// thread 1
{
    std::unique_lock<std::mutex> lck1(mt1, std::defer_lock);
    std::unique_lock<std::mutex> lck2(mt2, std::defer_lock);
    std::lock(lck1, lck2);
    // do something
}
// thread 2
{
    std::unique_lock<std::mutex> lck1(mt1, std::defer_lock);
    std::unique_lock<std::mutex> lck2(mt2, std::defer_lock);
    std::lock(lck2, lck1);
    // do something
}

此外std::lock和std::try_lock还是异常安全的函数(要求待加锁的对象unlock操作不允许抛出异常),当对多个对象加锁时,其中如果有某个对象在lock或try_lock时抛出异常,std::lock或std::try_lock会捕获这个异常并将之前已经加锁的对象逐个执行unlock操作,然后重新抛出这个异常(异常中立)。并且std::lock_guard的构造函数lock_guard(mutex_type& m, std::adopt_lock_t t)也不会抛出异常。所以std::lock像下面这么用也是正确

1
2
3
std::lock(mt1, mt2);
std::lock_guard<std::mutex> lck1(mt1, std::adopt_lock);
std::lock_guard<std::mutex> lck2(mt2, std::adopt_lock);

scope_lock与lock_guard区别的更多相关文章

  1. C++ 并发编程,std::unique_lock与std::lock_guard区别示例

    背景 平时看代码时,也会使用到std::lock_guard,但是std::unique_lock用的比较少.在看并发编程,这里总结一下.方便后续使用. std::unique_lock也可以提供自动 ...

  2. C++11 std::unique_lock与std::lock_guard区别及多线程应用实例

    C++多线程编程中通常会对共享的数据进行写保护,以防止多线程在对共享数据成员进行读写时造成资源争抢导致程序出现未定义的行为.通常的做法是在修改共享数据成员的时候进行加锁--mutex.在使用锁的时候通 ...

  3. std::unique_lock与std::lock_guard区别示例

    std::lock_guard std::lock_guard<std::mutex> lk(frame_mutex); std::unique_lock<std::mutex> ...

  4. std::unique_lock与std::lock_guard分析

    背景 C++多线程编程中通常会对共享的数据进行写保护,以防止多线程在对共享数据成员进行读写时造成资源争抢,导致程序出现未定义或异常行为.通常的做法是在修改共享数据成员时进行加锁(mutex).在使用锁 ...

  5. C++ 11 多线程下std::unique_lock与std::lock_guard的区别和用法

    这里主要介绍std::unique_lock与std::lock_guard的区别用法 先说简单的 一.std::lock_guard的用法 std::lock_guard其实就是简单的RAII封装, ...

  6. std::unique_lock<std::mutex> or std::lock_guard<std::mutex> C++11 区别

    http://stackoverflow.com/questions/20516773/stdunique-lockstdmutex-or-stdlock-guardstdmutex The diff ...

  7. boost::unique_lock和boost::lock_guard的区别

    lock_guard unique_lock boost::mutex mutex; boost::unique_lock<boost::mutex> lock(mutex); std:: ...

  8. C++11中lock_guard和unique_lock的区别

    c++11中有一个区域锁lock_guard,还有第二个区域锁unique_lock. 区域锁lock_guard使用起来比较简单,除了构造函数外没有其他member function,在整个区域都有 ...

  9. std::lock_guard和std::unique_lock的区别

    std::lock_guard 1 初始化的时候锁定std::mutex std::mutex m_mtx; std::lock_guard<std::mutex> m_lock(m_mt ...

随机推荐

  1. TypeScript笔记 5--变量声明(解构和展开)

    解构是什么 解构(destructuring assignment)是一种表达式,将数组或者对象中的数据赋给另一变量. 在开发过程中,我们经常遇到这样问题,需要将对象某个属性的值赋给其它两个变量.代码 ...

  2. java中的分支结构 switch case的使用

    switch(A),括号中A的取值只能是整型或者可以转换为整型的数值类型,比如byte.short.int.char.string(jdk1.7后加入)还有枚举:需要强调的是:long是不能用在swi ...

  3. Typescript学习笔记

    什么是 TypeScript TypeScript 是 JavaScript 的类型的超集,它可以编译成纯 JavaScript. 安装 TypeScript 命令行工具安装: npm install ...

  4. WatchKit编程指南:Watch Apps--文本、标签以及图片

    文本和分类标签 为了在Watch app中展示文本,使用标签对象.分类标签支持格式化的文本,可以在运行时被程序修改. 要添加标签到界面控制器,可以把它拖到对应的故事版场景(storyboard),在这 ...

  5. Thrift之TProtocol系列TCompactProtocol解析

    TCompactProtocol协议作为TBinaryProtocol协议的升级强化版,都作为二进制编码传输方式,采用了一种乐器MIDI文件的编码方法(wiki,百度下),简单介绍下两种思想: 1: ...

  6. Java中实例方法、类方法和构造方法

    类方法,有static修饰符,典型的主函数public static void main(String[] args){}实例方法,就是一般的方法构造方法,没有返回值(就是连void都没有),方法名与 ...

  7. linux 巨页使用测试

    这里记录测试巨页的mmap使用,测试代码和<linux 巨页使用测试以及勘误1>类似. 跟踪脚本如下: probe kernel.function("hugetlb_reserv ...

  8. WebSphere--安装与配置

    对于任何软件,都需要一些计划和具体步骤以确保成功安装.对于安装与配制 WebSphere应用服务器及其组件也是如此.下面介绍在Windows NT 上安装与配置WebSphere应用服务器     1 ...

  9. Win2003 设置远程连接限制数

    在开发过程中,很多同事需要连接到一台Win2003服务器,但是连接人数超过了10个,就连接不上了.想设置一下连接限制数,可以如下操作: 1:在运行里面输入gpedit.msc后,弹出"本地计 ...

  10. Linux安装JDK、MySQL和Tomcat

    1 依赖的安装 因为JDK.Tomcat和MySQL的安装过程中需要从网上下载部分支持包才可以继续,所以需要提前安装好依赖. yum install glibc.1686 yum -y install ...