首页 > 其他分享 >并发支持库:条件变量

并发支持库:条件变量

时间:2024-03-14 15:47:43浏览次数:24  
标签:std 变量 并发 lock 支持 try 互斥 guard mutex

互斥

std::mutex(c++11)

作用:互斥锁,提供一种原子操作,保护共享数据被多个线程访问的安全性

#include <mutex>
 
std::mutex mutex;

{
std::lock_guard<std::mutex> lock(mutex);
// operate data
};

成员函数

lock      锁定互斥,若互斥不可用则阻塞
try_lock  尝试锁定互斥,若互斥不可用则返回
unlock    解锁互斥

note:

一般不推荐直接使用其成员函数实现代码逻辑,推荐使用std::unique_lockstd::lock_guard 或 std::scoped_lock (C++17 起) 以更加异常安全的方式管理锁定。

std::lock_guard 是一种RAII风格的机制,它可以利用变量生命周期的作用域机制,在构造函数中实现mutex.lock(),析构函数中实现mutex.unlock()

std::timed_mutex(c++11)

作用:同mutex作用一样,只不过增加定时功能,尝试锁定互斥,若互斥在指定的时限段或时间点中不可用则返回false, 否则返回true

成员函数

lock 			锁定互斥,若互斥不可用则阻塞
try_lock 		尝试锁定互斥,若互斥不可用则返回[立即返回]
try_lock_for	尝试锁定互斥,若互斥在指定的时限时期中不可用则返回[时间段]
try_lock_until  尝试锁定互斥,若直至抵达指定时间点互斥不可用则返回[时间点]
unlock 			解锁互斥

使用方法

if(mutex.try_lock_for(100ms)){
// do something
mutex.unlock();
}else{
	std::cout<<"failed";
}

std::recursive_mutex(c++11)

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

成员函数

lock      锁定互斥,若互斥不可用则阻塞
try_lock  尝试锁定互斥,若互斥不可用则返回
unlock    解锁互斥

使用场景:在类的成员函数可能相互调用的可能情况下,保护类中的共享数据,防止死锁

#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::lock_guard<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;
}

std::recursive_timed_mutex(c++11)

作用:递归限时锁

成员函数

lock 			锁定互斥,若互斥不可用则阻塞
try_lock 		尝试锁定互斥,若互斥不可用则返回[立即返回]
try_lock_for	尝试锁定互斥,若互斥在指定的时限时期中不可用则返回[时间段]
try_lock_until  尝试锁定互斥,若直至抵达指定时间点互斥不可用则返回[时间点]
unlock 			解锁互斥

std::shared_mutex(c++17)

在<shared_mutex>定义

相当unix系统API中的:读写锁

与其他独占性的mutex不同的,share_mutex拥有两个层次的访问:

  • 共享(std::share_lock)- 多个线程能共享同一mutex的所有权
  • 独占性(std::unique_lock)- 仅一个线程能占有mutex

若一个线程已经获取独占性锁(通过lock、try_lock),则无其他线程能获取该锁(包括 共享的)

仅当任何线程均为取得独占性锁时,共享锁能被多个线程获取(通过lock_shared、try_lock_shared)

在一个线程内,同一时刻只能获取一个锁(共享或独占性)

share mutex在能由任何数量的线程同时读取共享数据,但一个线程只能在无其他线程同时读写时,写数据

(即读权限任意开放,写权限只允许同一时间原子操作,且写权限高于读权限,当写操作进行时,接下来的读操作将被阻塞)

成员函数

独占性锁(排他性锁定)
lock 		锁定互斥,若互斥不可用则阻塞
try_lock	尝试锁定互斥,若互斥不可用则返回
unlock		解锁互斥

共享锁定
lock_shared 		为共享所有权锁定互斥,若互斥不可用则阻塞
try_lock_shared		尝试为共享所有权锁定互斥,若互斥不可用则返回
unlock_shared		解锁互斥,即共享所有权

使用案例

int tick = 0;
// 定义一个share mutex
std::shared_mutex _mutex_lock;

void read(const std::string &thread_name)
{
    while()
    {
        {
            //读锁:std::share_lock
            std::shared_lock read_lock(_mutex_lock);
            std::cout<<thread_name<<"read:"<<tick<<std::endl;
        }
        using namespace std::chrono_literals;
        std::this_thread::sleep_for(50ms);
    }
}

void write(const std::string &thread_name)
{
    while(true)
    {
        {
            //写锁:std::unique_lock
            std::unique_lock write_lock(_mutex_lock);
            std::cout<< thread_name << "write:" << ++tick << std::endl; 
        }
        using namespace std::chrono_literals;
        std::this_thread::sleep_for(50ms);
    }
}

int main()
{
    std::thread read01{read,std::string("read01")},read02{read,std::string("read02")};
    std::thread write01{write,std::string("write01")};
    read01.join();
    read02.join();
    write01.join();
    return 0;
}

shared_timed_mutex(C++14)

<shared_mutex>

参考share_mutex

共享互斥通常用于多个读线程能同时访问同一资源而不导致数据竞争,但只有一个写线程能访问的情形。

成员函数

独占性锁(排他性锁定)
lock 			锁定互斥,若互斥不可用则阻塞
try_lock		尝试锁定互斥,若互斥不可用则返回
try_lock_for	尝试锁定互斥,若互斥在指定的时限时期中不可用则返回
try_lock_until  尝试锁定互斥,若直至抵达指定时间点互斥不可用则返回
unlock			解锁互斥

共享锁定
lock_shared 			为共享所有权锁定互斥,若互斥不可用则阻塞
try_lock_shared			尝试为共享所有权锁定互斥,若互斥不可用则返回
try_lcok_shared_for 	尝试为共享所有权锁定互斥,若互斥在指定的时限时期中不可用则返回
try_lock_shared_until	尝试为共享所有权锁定互斥,若直至抵达指定时间点互斥不可用则返回
unlock_shared			解锁互斥,即共享所有权

通用互斥(mutex)管理

std::lock_guard(c++11)

作用:严格实现基于作用域的mutex所有权包装器,缺点不灵活

定义如下,使用见上文std::mutex

使用了RAII,在其构造函数中使用mutex.lock(),析构函数中使用mutex.unlock();

template< class Mutex >
class lock_guard;

//模板形参
Mutex	-	要锁定的互斥。类型必须满足可基本锁定 (BasicLockable) 要求

std::scoped_lock(c++17)

作用:用于管理多个mutex,它在作用域的存在期间占有一或多个互斥,它使用了一个称为"死锁避免算法",可使用它避免死锁的算法

{
    std::scoped_lock lock(mutexes_[0], mutexes_[1], mutexes_[2], mutexes_[3], mutexes_[4]);
// do something
}

// 等价代码 1 (用 std::lock 和 std::lock_guard )
// std::lock(e1.m, e2.m);
// std::lock_guard<std::mutex> lk1(e1.m, std::adopt_lock);
// std::lock_guard<std::mutex> lk2(e2.m, std::adopt_lock);

// 等价代码 2 (若需要 unique_lock ,例如对于条件变量)
// std::unique_lock<std::mutex> lk1(e1.m, std::defer_lock);
// std::unique_lock<std::mutex> lk2(e2.m, std::defer_lock);
// std::lock(lk1, lk2);

std::unique_lock(C++11)

作用:实现可移动的mutex所有权包装器,对mutex进行更加灵活的权限管理

成员函数

锁定
lock			锁定关联互斥
try_lock		尝试锁定关联互斥,若互斥不可用则返回
try_lock_for 	试图锁定关联的定时可锁互斥,若互斥在给定时长中不可用则返回
try_lock_until	尝试锁定关联可定时锁互斥,若抵达指定时间点互斥仍不可用则返回
unlock			解锁关联互斥

修改器
swap 			与另一个std::unique_lock交换状态
release			将关联互斥解关联而不解锁它

观察器
mutex			返回指向关联互斥的指针
owns_lock		测试锁是否占有其关联互斥
operator bool  	同上

unique_lock和lock_guard的区别

  • unique_lock与lock_guard都能实现自动加锁和解锁,但是前者更加灵活,能实现更多的功能
  • unique_lock可以进行临时解锁和再上锁,如在构造对象之后使用lck.unlock()就可以进行解锁, lck.lock()进行上锁,而不必等到析构时自动解锁。lock_guard是不支持手动释放的
  • 一般来说,使用unique_lock比较多,除非追求极致的性能才会考虑使用lock_guard

使用场景

//std::lock_guard的缺陷
class LogFile {
    std::mutex _mu;
    ofstream f;
public:
    LogFile() {
        f.open("log.txt");
    }
    ~LogFile() {
        f.close();
    }
    void shared_print(string msg, int id) {
        {
            std::lock_guard<std::mutex> guard(_mu);
            //do something 1
        }
        //do something 2
        {
            std::lock_guard<std::mutex> guard(_mu);
            // do something 3
            f << msg << id << endl;
            cout << msg << id << endl;
        }
    }
};

//std::unique_lock的灵活处理
class LogFile {
    std::mutex _mu;
    ofstream f;
public:
    LogFile() {
        f.open("log.txt");
    }
    ~LogFile() {
        f.close();
    }
    void shared_print(string msg, int id) {
 
        std::unique_lock<std::mutex> guard(_mu);
        //do something 1
        guard.unlock(); //临时解锁
 
        //do something 2
 
        guard.lock(); //继续上锁
        // do something 3
        f << msg << id << endl;
        cout << msg << id << endl;
        // 结束时析构guard会临时解锁
        // 这句话可要可不要,不写,析构的时候也会自动执行
        // guard.ulock();
    }
 
};

std::shared_lock(c++14)

作用:实现可移动的共享mutex所有权封装器

std::share_lock是从共享(shared)的方式来进行加锁,当share mutex没有被其他线程锁持有或者以share_lock共享的方式持有,则该线程可以以共享的方式立即获取这把锁(对这把锁进行共享式上锁)

详情见std::shared_mutex的使用

用于指定锁定策略的标签类型(C++11)

defer_lock_t

try_to_lock_t

adopt_lock_t

用于指定锁定策略的标签常量(c++11)

std::defer_lockstd::try_to_lockstd::adopt_lock 分别是空结构体标签类型 std::defer_lock_tstd::try_to_lock_tstd::adopt_lock_t 的实例。

它们用于为 std::lock_guardstd::unique_lockstd::shared_lock 指定锁定策略。

defer_lock

不获得互斥的所有权

std::unique_lock < std::mutex > guard(_mu, std::defer_lock);

在guard的构造函数过程中,不对_mu进行 _mu.lock()操作,因此需要自己手动的使用其成员函数lock()

try_to_lock

尝试获得互斥的所有权而不阻塞

std::unique_lock < std::mutex > guard(_mu, std::try_to_lock_t);

adopt_lock

假设调用方线程已拥有互斥的所有权

std::lock_guardstd::mutex lock1(mutex,std::adopt_lock_t);

{
// 锁定两个互斥而不死锁, 注意:from.m&&to.m已经在这里上锁lock()
    std::lock(from.m, to.m);
    
// 保证二个已锁定互斥在作用域结尾解锁,将已经上锁的mutex的管理起来,在作用域结束的时候,自动unlock()
    std::lock_guard<std::mutex> lock1(from.m, std::adopt_lock);
    std::lock_guard<std::mutex> lock2(to.m, std::adopt_lock);
}

通用锁定算法

std::try_lock

尝试给每个可锁定的对象,调用try_lock。若try_lock失败,则对已经上锁的对象unclok,并返回第i个可锁对象的下标i,成功为-1.

std::mutex foo_count_mutex;
std::mutex bar_count_mutex;
c
if(-1 ==  std::try_lock(foo_count_mutex, bar_count_mutex.....))
{
//do something
  foo_count_mutex.unlock();
  bar_count_mutex.unlock();
}

std::lock

锁定指定的互斥体,若任何一个不可用则阻塞.

note: 锁定给定的可锁对象 lock1lock2...lockn ,用免死锁算法避免死锁。

        std::lock(e1.m, e2.m);
        std::lock_guard<std::mutex> lk1(e1.m, std::adopt_lock);
        std::lock_guard<std::mutex> lk2(e2.m, std::adopt_lock);
// 等价代码(若需要 unique_locks ,例如对于条件变量)
//        std::unique_lock<std::mutex> lk1(e1.m, std::defer_lock);
//        std::unique_lock<std::mutex> lk2(e2.m, std::defer_lock);
//        std::lock(lk1, lk2);

标签:std,变量,并发,lock,支持,try,互斥,guard,mutex
From: https://www.cnblogs.com/getonechao/p/18073000

相关文章

  • 并发支持库:原子操作类型
    原子操作这些组件为细粒度的原子操作提供,允许无锁并发编程。类型别名atomic_bool(C++11)std::atomic(typedef)atomic_char(C++11)std::atomic(typedef)atomic_schar(C++11)std::atomic(typedef)atomic_uchar(C++11)std::atomic(typedef)atomic_short(......
  • volatile关键字是如何确保多线程环境下变量的可见性和有序性
    VOLATILE关键字在JAVA中用于确保多线程环境下的变量可见性和一定程度的有序性,其具体实现机制基于JAVA内存模型(JAVAMEMORYMODEL,JMM):可见性:当一个线程修改了标记为volatile的共享变量时,它会强制将这个变量值从当前线程的工作内存刷新回主内存。同时,其他线程在读取该volatil......
  • 2.4GHz小型超高性能模块:LBEE5XV2EA-802、LBEE5PA1LD-005、LBES5PL2EL-923、LBWA0ZZ2DS
    1、描述:2EA型是一款基于CYW55573组合芯片组的小型超高性能模块,支持Wi-Fi802.11a/b/g/n/AC/ax2×2MIMO蓝牙5.3BR/EDR/le高达1.2Gbps的Wi-FiPHY数据速率和3Mbps的传统蓝牙PHY数据速率(EDR)以及2Mbps的PHY蓝牙LE数据速率。WLAN部分支持PCIe3.0第二代和SDIO3.0接口,蓝牙部分支持......
  • 滴水逆向笔记系列-c语言总结2-10.变量-11.if逆向-12.正向基础
    第十课c语言31.编码ASCII标准的ASCII编码只需要七位,第八位在拓展ASCII编码使用GB23122.局部变量和全局变量下面代码输出结果为1111(x=11改变了全局的x)第十一课c语言41.内存图2.逆向参数个数3.简单逆向if代码4.if...else...反汇编判断跳转执行一部分代......
  • Serializer 序列化 -----视图层传入一个变量到序列化器的方法
    fromrest_frameworkimportserializersclassMyModelSerializer(serializers.ModelSerializer):classMeta:model=MyModelfields=['field1','field2']defto_representation(self,instance):......
  • 从零开始写 Docker(六)---实现 mydocker run -v 支持数据卷挂载
    本文为从零开始写Docker系列第六篇,实现类似docker-v的功能,通过挂载数据卷将容器中部分数据持久化到宿主机。完整代码见:https://github.com/lixd/mydocker欢迎Star推荐阅读以下文章对docker基本实现有一个大致认识:核心原理:深入理解Docker核心原理:Namespace、Cgr......
  • MeterSphere接口自动化系列之动态设置全局变量
    一、问题描述:    所有场景接口参数中需要token,token是由登录生成,每次登录后token会改变,该如何动态获取token并提供给后续场景使用?二、问题分析:    针对该问题,需要考虑,每个场景执行前登录一次,还是所有场景执行前只登录一次。    基于上述的分析,......
  • SQL 必须声明标量变量
    原文链接:https://deepinout.com/sql/sql-questions/17_sql_must_declare_the_scalar_variable.html什么是标量变量?在SQL中,标量变量是用来存储和表示单个数据值的容器。这些数据值可以是数字、字符串、日期等。标量变量可以在SQL查询和存储过程中使用,可以进行赋值和获取值的操作......
  • 「Java开发指南」MyEclipse如何支持Spring Scaffolding?(五)
    在上文中(点击这里回顾>>)主要为大家介绍了SpringDSL模型等内容,本文将继续介绍菜单等。MyEclipsev2023.1.2离线版下载MyEclipse技术交流群:742336981欢迎一起进群讨论6.菜单本节主要描述与Spring支持的MyEclipse相关的各种菜单。6.1MyEclipse菜单当您右键单击Eclipse项目......
  • 图解Java并发编程第一章总结【精炼版】
    【第一章】图解Java并发编程Java线程的基本操作yield操作:yield操作,在基于时间片轮转的cpu调度算法中,用来放弃当前时间片sleep操作:sleep操作分为三种情况普通sleep:在指定时间内放弃cpu使用权,不释放同步锁sleep(0):作用与yield相同sleep被中断:抛出中断异常......