首页 > 其他分享 >并发支持库:互斥锁及其管理

并发支持库:互斥锁及其管理

时间:2024-03-14 15:48:10浏览次数:23  
标签: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/18072995

相关文章

  • 并发支持库:条件变量
    互斥std::mutex(c++11)作用:互斥锁,提供一种原子操作,保护共享数据被多个线程访问的安全性#include<mutex>std::mutexmutex;{std::lock_guard<std::mutex>lock(mutex);//operatedata};成员函数lock锁定互斥,若互斥不可用则阻塞try_lock尝试锁定互斥,若......
  • 并发支持库:原子操作类型
    原子操作这些组件为细粒度的原子操作提供,允许无锁并发编程。类型别名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(......
  • Java线程池参数详解及其示例
    线程池在Java并发编程中占据核心地位,通过复用线程资源,可以极大地提高系统资源利用率和响应速度。Java中的java.util.concurrent.ThreadPoolExecutor类提供了丰富的参数配置以满足不同场景的需求。下面我们将逐一介绍线程池的主要构建参数,并给出相应的例子说明:1.corePoolSi......
  • 图解Java并发编程第一章总结【精炼版】
    【第一章】图解Java并发编程Java线程的基本操作yield操作:yield操作,在基于时间片轮转的cpu调度算法中,用来放弃当前时间片sleep操作:sleep操作分为三种情况普通sleep:在指定时间内放弃cpu使用权,不释放同步锁sleep(0):作用与yield相同sleep被中断:抛出中断异常......
  • 一致性哈希算法及其在分布式系统中的应用
    摘要本文将会从实际应用场景出发,介绍一致性哈希算法(ConsistentHashing)及其在分布式系统中的应用。首先本文会描述一个在日常开发中经常会遇到的问题场景,借此介绍一致性哈希算法以及这个算法如何解决此问题;接下来会对这个算法进行相对详细的描述,并讨论一些如虚拟节点等与此算......
  • Java多线程&并发篇2024
    目录Java全技术栈面试题合集地址Java多线程&并发篇1.volatile能使得一个非原子操作变成原子操作吗?2.volatile修饰符的有过什么实践?3.volatile类型变量提供什么保证?4.Java中怎么获取一份线程dump文件?5.什么是线程局部变量?6.Java中sleep方法和wait方法的区别?7.......
  • DevOps软件开发管理模式、CICD概念及其Jenkins使用
    一、什么是DevOpsDevOps是Develop与Operations的缩写,是开发和运营维护的总称。它是企业内开发、技术运营和质量保障这三方面工作的融合,用于促进开发、技术运营和质保部门之间的沟通、协作与整合。为巩固软件设计与开发结果,将开发、运维与测试结合一起,形成了DevOps软件开发管......
  • JAVA的多线程及并发
    1.Java中实现多线程有几种方法继承Thread类;实现Runnable接口;实现Callable接口通过FutureTask包装器来创建Thread线程;使用ExecutorService、Callable、Future实现有返回结果的多线程(也就是使用了ExecutorService来管理前面的三种方......
  • mysql卸载安装及其报错解决
    数据库软件机制复杂,解决它的相关问题的时候最有效的方法是dos命令。在删除,修改密码和处理报错的时候,往往只要dos命令正确发挥作用,不会引发别的问题而若是直接对mysql下的各种文件进行修改极易引发未知问题,应谨慎。为不使安装过程产生报错应该先在以下几个方面进行清理。1......
  • 使用kubeadm部署Kubernetes 1.26及其它版本
    1.系统配置环境信息:系统:CentOSLinuxrelease7.6.1810(Core)k8s版本:1.26.0(可自己选择)IP主机名规划角色192.168.223.123auto-inspaction-1master192.168.223.68auto-inspaction-0node192.168.223.73auto-inspaction-2node在各个主机上完成下......