文档章节

C++ 锁

y
 yepanl
发布于 09/22 18:56
字数 1302
阅读 26
收藏 0

    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;
}

© 著作权归作者所有

共有 人打赏支持
y
粉丝 0
博文 56
码字总数 55172
作品 0
南京
程序员
私信 提问
C++雾中风景12:聊聊C++中的Mutex,以及拯救生产力的Boost

笔者近期在工作之中编程实现一个Cache结构的封装,需要使用到C++之中的互斥量Mutex,于是花了一些时间进行了调研。(结果对C++标准库很是绝望....)最终还是通过利用了Boost库的shared_mute...

LeeHappen
10/06
0
0
有人要我挑战STL 质量, 很简单.

我很多年没有碰C++了, 下面是10年前的代码, 模板来做到数据类型无关, 用于在多个进程当中实现循环链表,用于消息队列. 新的版本,用一个局部对象,来实现锁. 实现这个的目的是在于通过设置固定大...

宏哥
2012/10/12
9.8K
132
Thrift第七课 服务器多线程发送异常

场景 C++服务器端为每一个客户端建立多线程发送,没有添加锁,会触发异常:received invalid message type 3 from client。导致服务器端主动断开跟客户端的连接 打印出错的代码在TDispatchPr...

fengyuzaitu
05/02
0
0
UCommon 4.1.0 发布

UCommon 是一个轻量级的 C++ 库,使用 C++ 设计模式,适合用于嵌入式应用的开发,例如使用 uClibc 和 POSIX 线程支持。为了这个目的,UCommon 禁用了一些特别消耗内存的语言特性,引入了 Ob...

红薯
2011/01/23
502
0
object-c 多线程 加锁

object-c的多线程如java的多线程一样方便可靠。 一、线程创建与启动 线程创建主要有二种方式: [cpp] view plaincopy - (id)init; // designated initializer - (id)initWithTarget:(id)targ...

长平狐
2013/03/19
261
0

没有更多内容

加载失败,请刷新页面

加载更多

《稻盛和夫经营学》读后感心得体会3180字范文

《稻盛和夫经营学》读后感心得体会3180字范文: 一代日本经营之圣稻盛和夫凭借刻苦勤奋的精神以及深植于佛教的商业道德准则,成为了“佛系”企业家的代表人物。在《稻盛和夫经营学》“领导人...

原创小博客
29分钟前
1
0
java框架学习日志-5(常见的依赖注入)

依赖注入(dependency injection) 之前提到控制反转(Inversion of Control)也叫依赖注入,它们其实是一个东西,只是看的角度不同,这章详细说一下依赖注入。 依赖——指bean对象创建依赖于...

白话
45分钟前
2
0
红外接收器驱动开发

背景:使用系统的红外遥控软件没有反应,然后以为自己接线错误,反复测试,结果烧坏了一个红外接收器,信号主板没有问题。所以自己开发了一个红外接收器的python驱动。接线参见https://my.os...

mbzhong
今天
2
0
ActiveMQ消息传送机制以及ACK机制详解

AcitveMQ是作为一种消息存储和分发组件,涉及到client与broker端数据交互的方方面面,它不仅要担保消息的存储安全性,还要提供额外的手段来确保消息的分发是可靠的。 一. ActiveMQ消息传送机...

watermelon11
今天
2
0
HashTable和Vector为什么逐渐被废弃

HashTable,不允许键值为null,还一个就是put方法使用sychronized方法进行线程同步,单线程无需同步,多线程可用concurren包的类型。 如编程思想里面说的作为工具类,封闭性做的不好没有一个...

noob_chr
昨天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部