文档章节

C++ 锁

y
 yepanl
发布于 09/22 18:56
字数 1302
阅读 9
收藏 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
博文 51
码字总数 33563
作品 0
南京
程序员
C++雾中风景12:聊聊C++中的Mutex,以及拯救生产力的Boost

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

LeeHappen
10/06
0
0
Thrift第七课 服务器多线程发送异常

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

fengyuzaitu
05/02
0
0
C语言对电脑做些小事儿之C语言扭曲窗口屏幕,电脑屏幕也会下垂

C语言对电脑做些小事儿之C语言扭曲窗口屏幕,电脑屏幕也会下垂 今天分享给大家的l两例比较短小精悍,都是针对于计算机屏幕处理的两例,有兴趣的可以放到自己电脑上运行哦。绝对无害,健康编程...

这个人很懒什么都没留下
07/19
0
0
拿Erlang来拯救谁?---Erlang学习总结一

工作中主要使用C/C++作为开发语言。 也曾在PHP,JAVA,JS,LUA上逗留过,但对它们,我没有花费过多的精力。部分原因可能是由于工作不能实际使用这些语言来开发项目,而我的理解是它们对我的吸引...

逍遥客
2011/11/08
0
1
CSDN回帖得分大全(近两年)

√ vs2005调用dll的时候Initialize()函数返回错误 [VC/MFC 基础类] √ 为什么我创建登陆框之后,然后获取登陆框的数据时候总是出现非法操作! [VC/MFC 界面] √ CFileFind::FindFile 支持通配...

junwong
2012/03/09
0
0

没有更多内容

加载失败,请刷新页面

加载更多

CentOS7防火墙firewalld操作

firewalld Linux上新用的防火墙软件,跟iptables差不多的工具。 firewall-cmd 是 firewalld 的字符界面管理工具,firewalld是CentOS7的一大特性,最大的好处有两个:支持动态更新,不用重启服...

dingdayu
今天
1
0
关于组件化的最初步

一个工程可能会有多个版本,有国际版、国内版、还有针对各种不同的渠道化的打包版本、这个属于我们日常经常见到的打包差异化版本需求。 而对于工程的开发,比如以前的公司,分成了有三大块业...

DannyCoder
今天
2
0
Spring的Resttemplate发送带header的post请求

private HttpHeaders getJsonHeader() { HttpHeaders headers = new HttpHeaders(); MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8"); ......

qiang123
昨天
3
0
Spring Cloud Gateway 之 Only one connection receive subscriber allowed

都说Spring Cloud Gateway好,我也来试试,可是配置了总是报下面这个错误: java.lang.IllegalStateException: Only one connection receive subscriber allowed. 困扰了我几天的问题,原来...

ThinkGem
昨天
26
0
学习设计模式——观察者模式

1. 认识观察者模式 1. 定义:定义对象之间一种一对多的依赖关系,当一个对象状态发生变化时,依赖该对象的其他对象都会得到通知并进行相应的变化。 2. 组织结构: Subject:目标对象类,会被...

江左煤郎
昨天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部