C++11 互斥锁

news2025/8/6 4:07:20

目录

互斥锁

2.1 独占互斥量std::mutex

std::mutex使用Demo 

2.2 递归互斥量std::recursive_mutex

死锁demo

使用std::recursive_mutex递归锁 

2.3 带超时的互斥量

std::timed_mutexDemo

 2.4 lock_guard和unique_lock的使用和区别

 unique_lock,lock_guard的使用

 unique_lock,lock_guard的区别

总结


C/C++Linux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂

互斥锁

mutex又称互斥量,C++ 11中与 mutex相关的类(包括锁类型)和函数都声明在头文件中,所以需要使用 std::mutex,就必须包含头文件。

C++11提供如下4种语义的互斥量(mutex)

  1. std::mutex,独占的互斥量,不能递归使用。
  2. std::time_mutex,带超时的独占互斥量,不能递归使用。
  3. std::recursive_mutex,递归互斥量,不带超时功能。
  4. std::recursive_timed_mutex,带超时的递归互斥量。

2.1 独占互斥量std::mutex

std::mutex 介绍

        下面以 std::mutex 为例介绍 C++11 中的互斥量用法。

        std::mutex 是C++11 中最基本的互斥量,std::mutex 对象提供了独占所有权的特性——即不支持递归地对 std::mutex 对象上锁,而 std::recursive_lock 则可以递归地对互斥量对象上锁。

std::mutex 的成员函数

1. 构造函数,std::mutex不允许拷贝构造,也不允许 move 拷贝,最初产生的 mutex 对象是处于unlocked 状态的。

2. lock(),调用线程将锁住该互斥量。线程调用该函数会发生下面 3 种情况:

(1). 如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用 unlock之前,该线程一直拥有该锁。

(2). 如果当前互斥量被其他线程锁住,则当前的调用线程被阻塞住。

(3). 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)。

3. unlock(), 解锁,释放对互斥量的所有权。

4. try_lock(),尝试锁住互斥量,如果互斥量被其他线程占有,则当前线程也不会被阻塞。线程调用该函数也会出现下面 3 种情况,

(1). 如果当前互斥量没有被其他线程占有,则该线程锁住互斥量,直到该线程调用 unlock 释放互斥量。

(2). 如果当前互斥量被其他线程锁住,则当前调用线程返回false,而并不会被阻塞掉。

(3). 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)。

std::mutex使用Demo 

//mutex1
//g++ mutex1.cpp -std=c++14 -lpthread
#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex

volatile int counter(0); // non-atomic counter
std::mutex mtx;           // locks access to counter

void increases_10k()
{
    for (int i=0; i<10000; ++i) {
        // 1. 使用try_lock的情况
//        if (mtx.try_lock()) {   // only increase if currently not locked:
//            ++counter;
//            mtx.unlock();
//        }
        // 2. 使用lock的情况
                {
                    mtx.lock();
                    ++counter;
                    mtx.unlock();
                }
    }
}

int main()
{
    std::thread threads[10];
    for (int i=0; i<10; ++i)
        threads[i] = std::thread(increases_10k);

//等待线程退出
    for (auto& th : threads) th.join();
    std::cout << " successful increases of the counter "  << counter << std::endl;

    return 0;
}

 

2.2 递归互斥量std::recursive_mutex

        递归锁允许同一个线程多次获取该互斥锁,可以用来解决同一线程需要多次获取互斥量时死锁的问题。

        虽然递归锁能解决这种情况的死锁问题,但是尽量不要使用递归锁,主要原因如下:

1. 需要用到递归锁的多线程互斥处理本身就是可以简化的,允许递归很容易放纵复杂逻辑的产生,并且产生晦涩,当要使用递归锁的时候应该重新审视自己的代码是否一定要使用递归锁;

2. 递归锁比起非递归锁,效率会低;

3. 递归锁虽然允许同一个线程多次获得同一个互斥量,但可重复获得的最大次数并未具体说明,一旦超过一定的次数,再对lock进行调用就会抛出std::system错误。

死锁demo

//死锁范例mutex2-dead-lock
#include <iostream>
#include <thread>
#include <mutex>

struct Complex
{
    std::mutex mutex;
    int i;

    Complex() : i(0){}

    void mul(int x)
    {
        std::lock_guard<std::mutex> lock(mutex);
        i *= x;
    }

    void div(int x)
    {
        std::lock_guard<std::mutex> lock(mutex);
        i /= x;
    }

    void both(int x, int y)
    {
        //lock_guard 构造函数加锁, 析构函数释放锁
        std::lock_guard<std::mutex> lock(mutex);
        mul(x); // 获取不了锁
        div(y);
    }

    void init()
    {
        //lock_guard 构造函数加锁, 析构函数释放锁
        std::lock_guard<std::mutex> lock(mutex);
        sub_init();
    }
    void sub_init()
    {
        std::lock_guard<std::mutex> lock(mutex);
    }
};

int main(void)
{
    Complex complex;

    complex.both(32, 23);
    std::cout << "main finish\n";
    return 0;
}

        运行后出现死锁的情况。在调用both时获取了互斥量,在调用mul时又要获取互斥量,但both的并没有释放,从而产生死锁。

使用std::recursive_mutex递归锁 

//递归锁recursive_mutex1
#include <iostream>
#include <thread>
#include <mutex>

struct Complex
{
    std::recursive_mutex mutex;
    int i;

    Complex() : i(0){}

    void mul(int x)
    {
        std::lock_guard<std::recursive_mutex> lock(mutex);
        i *= x;
    }

    void div(int x)
    {
        std::unique_lock<std::recursive_mutex> lock(mutex);
        i /= x;
    }

    void both(int x, int y)
    {
        std::lock_guard<std::recursive_mutex> lock(mutex);
        mul(x);
        div(y);
    }
};

int main(void)
{
    Complex complex;

    complex.both(32, 23);  //因为同一线程可以多次获取同一互斥量,不会发生死锁

    std::cout << "main finish\n";
    return 0;
}

2.3 带超时的互斥量

std::timed_mutex和std::recursive_timed_mutex

        std::timed_mutex比std::mutex多了两个超时获取锁的接口:try_lock_for和try_lock_until。

try_lock_for:尝试获取互斥锁。阻塞直到指定timeout_duration时间过去或获得锁。

try_lock_until:尝试获取互斥锁。阻塞直到timeout_time达到指定或获得锁

std::timed_mutexDemo

#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
std::timed_mutex mutex;
void work() {
	std::chrono::milliseconds timeout(100);
	while (true) {
		if (mutex.try_lock_for(timeout)) {
			std::cout << std::this_thread::get_id() << ": do work with the mutex" << std::endl;
			std::chrono::milliseconds sleepDuration(250);
			std::this_thread::sleep_for(sleepDuration);
			mutex.unlock();
			std::this_thread::sleep_for(sleepDuration);
		} else {
			std::cout << std::this_thread::get_id() << ": do work without the mutex" << std::endl;
			std::chrono::milliseconds sleepDuration(100);
			std::this_thread::sleep_for(sleepDuration);
		}
	}
}
int main(void) {
	std::thread t1(work);
	std::thread t2(work);
	t1.join();
	t2.join();
	std::cout << "main finish\n";
	return 0;
}

 2.4 lock_guard和unique_lock的使用和区别

        相对于手动lock和unlock,我们可以使用RAII(通过类的构造析构)来实现更好的编码方式。lock_guard和unique_lock ,在构造变量的时候,会去尝试上锁lock,在作用域消失,生命周期结束的时候会析构,这个时候就会解锁,unlock。

防止忘记释放锁

 unique_lock,lock_guard的使用

这里涉及到unique_lock,lock_guard的使用。lock_guard换成unique_lock是一样的。

#include <iostream> // std::cout
#include <thread> // std::thread
#include <mutex> // std::mutex, std::lock_guard
#include <stdexcept> // std::logic_error

std::mutex mtx;

void print_even (int x) {
	if (x%2==0) {
		std::cout << x << " is even\n";
	} else {
		//奇数throw 
		throw (std::logic_error("not even"));
	}
}

void print_thread_id (int id) {
	try {
		//std::lock_guard<std::mutex> lck (mtx);
		std::unique_lock<std::mutex> lck (mtx);
		print_even(id);
	} catch (std::logic_error&) {
		//捕获异常
		std::cout << "[exception caught]\n";
	}
}

int main () {
	std::thread threads[10];
	// spawn 10 threads:
	for (int i=0; i<10; ++i) {
		threads[i] = std::thread(print_thread_id,i+1);
	}

	for (auto& th : threads) {
		th.join();
	}
	return 0;
}

 unique_lock,lock_guard的区别

        unique_lock与lock_guard都能实现自动加锁和解锁,但是前者更加灵活,能实现更多的功能。

        unique_lock可以进行临时解锁和再上锁,如在构造对象之后使用lck.unlock()就可以进行解锁,lck.lock()进行上锁,而不必等到析构时自动解锁。

#include <iostream>
#include <deque>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <unistd.h>
std::deque<int> q;
std::mutex mu;
std::condition_variable cond;
int count = 0;
void fun1() {
	while (true) {
		std::unique_lock<std::mutex> locker(mu);
		
		q.push_front(count++);
		
		locker.unlock(); 
		
		cond.notify_one();
		sleep(1);
	}
}
void fun2() {
	while (true) {
		std::unique_lock<std::mutex> locker(mu);
		
		cond.wait(locker, []() {
			return !q.empty();
		});
		auto data = q.back();
		q.pop_back();
		
		//locker.unlock(); 可以调用也可以不调用,下一次循环之前的locker会析构,里面也会调用unlock
		std::cout << "thread2 get value form thread1: " << data << std::endl;
	}
}
int main() {

	std::thread t1(fun1);
	std::thread t2(fun2);
	t1.join();
	t2.join();
	return 0;
}

        条件变量的目的就是为了,在没有获得某种提醒时长时间休眠; 如果正常情况下, 我们需要一直循环(+sleep), 这样的问题就是CPU消耗+时延问题,条件变量的意思是在cond.wait这里一直休眠直到cond.notify_one唤醒才开始执行下一句; 还有cond.notify_all()接口用于唤醒所有等待的线程。

那么为什么必须使用unique_lock呢?

        原因: 条件变量在wait时会进行unlock再进入休眠, lock_guard并无该操作接口

        wait: 如果线程被唤醒或者超时那么会先进行lock获取锁, 再判断条件(传入的参数)是否成立, 如果成立则wait函数返回否则释放锁继续休眠

总结

      使用场景:需要结合notify+wait的场景使用unique_lock; 如果只是单纯的互斥使用lock_guard

lock_guard

1.std::lock_guard 在构造函数中进行加锁,析构函数中进行解锁。

2.锁在多线程编程中,使用较多,因此c++11提供了lock_guard模板类;在实际编程中,我们也可以根据自己的场景编写resource_guard RAII类,避免忘掉释放资源。

std::unique_lock

1. unique_lock 是通用互斥包装器,允许延迟锁定、锁定的有时限尝试、递归锁定、所有权转移和与条件变量一同使用。

2. unique_lock比lock_guard使用更加灵活,功能更加强大。

3. 使用unique_lock需要付出更多的时间、性能成本。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/15794.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据结构】栈和队列OJ练习

文章目录1. 栈1.1 栈的概念及其结构1.2 栈的实现Stack.hStack.cTest.c2. 队列2.1 队列的概念及其结构2.2 队列的实现Queue.hQueue.cTest.c3. 栈和队列面试题3.1 括号匹配问题3.2 用队列实现栈3.3 用栈实现队列3.4 设计循环队列1. 栈 1.1 栈的概念及其结构 &#x1f436; 栈的…

《深度学习进阶 自然语言处理》第六章:LSTM介绍

文章目录6.1 RNN的问题6.1.1 RNN的复习6.1.2 梯度消失和梯度爆炸6.1.4 梯度爆炸的对策6.2 梯度消失和LSTM6.2.1 LSTM的接口6.2.2 LSTM层的结构6.2.3 输出门6.2.4 遗忘门6.2.5 新的记忆单元6.2.6 输入门6.2.7 LSTM的梯度的流动6.3 使用LSTM的语言模型6.3.1 LSTM层的多层化6.3.2 …

乐华娱乐欲重返上市:毛利率走低,上半年利润下滑,韩庚为股东

撰稿|汤汤 来源|贝多财经 宣布暂缓香港IPO计划不到3个月&#xff0c;乐华娱乐&#xff08;HK:02306&#xff09;欲再度回归。 11月17日&#xff0c;港交所披露的信息显示&#xff0c;乐华娱乐集团&#xff08;YH Entertainment Group&#xff0c;简称“乐华娱乐”&#xff0…

谈谈主数据管理的概念、原则、标准和指南

1主数据的定义和关键概念 1.1什么是主数据 主数据是不同业务领域的公共信息,并在多个业务流程中使用。主数据通常描述参与事务或事件的事物。示例包括有关课程、学生或雇员的信息。 参考数据通常用于对其他数据(如状态代码)进行分类,或与组织边界以外的信息(如国家列表)相…

ctfhub -afr -1 2 3

afr-1 打开题目链接 默认的传参数据为 ?phello 更改一下试试看 ?p111 无回显 ?pflag 回显了 no no no 想到了 php任意文件读取 ?pphp://filter/readconvert.base64-encode/resourceflag 回显出数据 应该是base64 拿去解码 得到flag afr-2 打开题目链接 查看源代码…

【第一阶段:java基础】第8章:面向对象编程高级-1(P333-P393)static、main、代码块、单例设计模式

本系列博客是韩顺平老师java基础课的课程笔记&#xff0c;B站&#xff1a;课程链接&#xff0c;吐血推荐的一套全网最细java教程&#xff0c;获益匪浅&#xff01; 韩顺平P333-P393类变量和类方法类变量/静态变量类方法/静态方法理解main方法语法代码块注意事项好处细节单例设计…

Java接口

什么是接口&#xff1f; 在Java中&#xff0c;接口可以看成是多个类的公共规范&#xff0c;是一种引用数据类型。 使用关键字interface来定义接口 interface IRunning {void run(); }在创建接口时&#xff0c;接口的命名一般以大写字母I开头&#xff0c;接口的命名一般使用形…

【数据结构】栈和队列

文章目录栈和队列栈栈的概念及结构栈的实现初始化栈入栈出栈获取栈顶元素获取栈中有效元素个数判断栈是否为空销毁栈括号匹配问题队列队列的概念及结构队列的实现初始化队列队尾入队列对头出队列获取队头元素获取队尾元素销毁队列判断队列是否为空栈和队列 栈 栈的概念及结构…

带你初识微服务

博客主页&#xff1a;踏风彡的博客 博主介绍&#xff1a;一枚在学习的大学生&#xff0c;希望在这里和各位一起学习。 所属专栏&#xff1a;SpringCloud 文章创作不易&#xff0c;期待各位朋友的互动&#xff0c;有什么学习问题都可在评论区留言或者私信我&#xff0c;我会尽我…

同花顺_代码解析_技术指标_L

本文通过对同花顺中现成代码进行解析&#xff0c;用以了解同花顺相关策略设计的思想 目录 LH_LYDG LH_猎鹰歼狐 LHBLX LHSJ LHTZ LHXJ LH猎狐雷达 LOF净值 LWR LH_LYDG 猎鹰渡关&#xff08;检测大盘顶部&#xff09; 指标用法&#xff1a; &#xff08;1&#xff…

进程间的信号

目录 一.信号入门 1.1概念 1.2信号发送与记录 1.3信号的处理方式 二.产生信号的方式 2.1通过终端按键产生 2.2通过系统函数向进程发信号 2.3由软件条件产生信号 2.4由硬件异常产生信号 三.阻塞信号 3.1信号相关概念 3.2信号在内核的表示 3.3sigset_t&#xff1a; 3.4…

工具及方法 - 使用Total Commander来查找重名文件

我只是一个Total Commander的轻度使用者&#xff0c;主要使用的是打开多个窗口&#xff0c;可以方便的以标签形式切换。 还有&#xff0c;这个软件是免费的&#xff0c;只是免费版打开时多一步&#xff0c;要输入个数字验证。 今天在使用一个SDK时&#xff0c;要包含进很多头文…

论文阅读笔记《Locality Preserving Matching》

核心思想 该文提出一种基于局部保持的特征匹配方法&#xff08;LPM&#xff09;。其核心思想是对于一个正确匹配点&#xff0c;其邻域范围内的其他匹配点与对应目标点之间的变换关系&#xff0c;应该和正确的匹配点保持一致&#xff0c;而错误匹配点&#xff0c;则应该有较大的…

第一章《初学者问题大集合》第6节:IntelliJ IDEA的下载与安装

当完成了Java开发环境之后&#xff0c;各位读者就可以开始编写第一个Java程序了。可是应该在哪里写程序呢&#xff1f;早期的开发者们都是用纯文本编辑工具编写Java程序&#xff0c;并且在命令行窗口中编译和运行Java程序。时至今日&#xff0c;我们早已远离了那个程序开发的“…

CentOs程序环境准备

1. MySQL的安装启动 选择指定操作系统指定版本的mysql进行下载 MySQL :: Download MySQL Community Serverhttps://dev.mysql.com/downloads/mysql/5.7.html#downloads 选择复制下载链接 回到终端&#xff0c;执行此命令下载 wget https://dev.mysql.com/get/Downloads/MyS…

举个栗子~Tableau 技巧(244):用和弦图(Chord diagram)呈现数据关系

关于和弦图 和弦图&#xff08;Chord diagram&#xff09;常用来表示数据之间的相互关系。数据点沿着圆圈分布&#xff0c;通过点和点之间相互连接的弧线来呈现相互之间的关系。和弦图从视觉上来说比较美观&#xff0c;数据呈现又很直观&#xff0c;所以深受数据粉喜爱。 之前…

【Linux初阶】Linux调试器-gdb使用 | gdb的 l/b/info/d/r/n/s/bt/finish/p/(un)display/q

&#x1f31f;hello&#xff0c;各位读者大大们你们好呀&#x1f31f; &#x1f36d;&#x1f36d;系列专栏&#xff1a;【Linux初阶】 ✒️✒️本篇内容&#xff1a;gdb使用相关背景知识&#xff0c;gdb的使用&#xff08;打断点、查断点、消断点、调试运行、查看对应变量&…

【python拼图游戏】图片自选,来挑战一下自己的极限吧~

嗨害大家好鸭&#xff01;我是小熊猫❤ 拼图的画面多以自然风光、建筑物以及一些为人所熟识的图案的为题材。 城堡和山峦是两类传统的主题&#xff0c; 不过任何图画和影像都可以用做拼图的素材。 有一些公司还提供将私人摄影作品制成拼图的服务。 今天我小熊猫就给带来py…

FFmpeg5.1 解码rtsp 并用OpenCV 播放

RTSP 连接过程如下图 看下实际过程中FFmpeg 的日志情况&#xff1a; [tcp 0000014CC3256D40] No default whitelist set [tcp 0000014CC3256D40] Original list of addresses: [tcp 0000014CC3256D40] Address ::1 port 8554 [tcp 0000014CC3256D40] Address 127.0.0.1 po…

使用BP神经网络、RBF神经网络以及PSO优化的RBF神经网络对数据进行预测(Matlab代码实现)

&#x1f352;&#x1f352;&#x1f352;欢迎关注&#x1f308;&#x1f308;&#x1f308; &#x1f4dd;个人主页&#xff1a;我爱Matlab &#x1f44d;点赞➕评论➕收藏 养成习惯&#xff08;一键三连&#xff09;&#x1f33b;&#x1f33b;&#x1f33b; &#x1f34c;希…