协程库-锁类-实现线程互斥同步

news2024/12/26 21:09:18

mutex.h:信号量,互斥锁,读写锁,范围锁模板,自旋锁,原子锁

**锁不能进行拷贝操作:**锁是用于管理多线程并发访问共享资源的同步原语。这些锁包括互斥锁(mutex)、读写锁(rwlock)等。它们通常不支持拷贝构造和拷贝赋值,这是为了防止在一个线程持有锁的情况下,另一个线程通过拷贝得到相同的锁,从而可能导致死锁或数据不一致的问题。

《Effective C++》 条款 06

想要禁止⼀个类对象的拷⻉操作,就要禁⽌拷⻉构造函数和拷⻉赋值运算符。
解决⽅案2:
定义⼀个基类专⻔阻⽌拷⻉动作,继承该基类的派生类起实例化对象也就无法进行拷⻉操作。

/**
 * @file noncopyable.h
 * @brief 不可拷贝对象封装
 */
#ifndef __Fzk_NONCOPYABLE_H__
#define __Fzk_NONCOPYABLE_H__
namespace FzkCoroutine {
/**
 * @brief 对象无法拷贝,赋值
 */
class Noncopyable {
public:
    /**
     * @brief 默认构造函数
     */
    Noncopyable() = default;
    /**
     * @brief 默认析构函数
     */
    ~Noncopyable() = default;
    /**
     * @brief 拷贝构造函数(禁用)
     */
    Noncopyable(const Noncopyable&) = delete;
    /**
     * @brief 赋值函数(禁用)
     */
    Noncopyable& operator=(const Noncopyable&) = delete;
};
}
#endif

基于pthread进一步封装了信号量,互斥锁,读写锁,范围锁模板,自旋锁,原子锁。

局部锁模板:

采用RAII编程风格,RAII的核心思想是利用对象的生命周期来管理资源,确保资源在对象的构造函数中被获取,并在析构函数中被释放。
ScopedLockImpl 及其派生类通过在构造时获取资源(加锁)并在析构时释放资源(解锁)

/**
 * @brief 局部锁的模板实现
 */
template<class T>
struct ScopedLockImpl {
public:
    /**
     * @brief 构造函数
     * @param[in] mutex Mutex
     */
    ScopedLockImpl(T& mutex)
        :m_mutex(mutex) {
        m_mutex.lock();
        m_locked = true;
    }
    /**
     * @brief 析构函数,自动释放锁
     */
    ~ScopedLockImpl() {
        unlock();
    }
    /**
     * @brief 加锁
     */
    void lock() {
        if(!m_locked) {
            m_mutex.lock();
            m_locked = true;
        }
    }
    /**
     * @brief 解锁
     */
    void unlock() {
        if(m_locked) {
            m_mutex.unlock();
            m_locked = false;
        }
    }
private:
    /// mutex
    T& m_mutex;
    /// 是否已上锁
    bool m_locked;
};

类似于C++的lock_guard:

lock_guard通过与互斥锁(mutex)结合使用来实现线程同步。当创建一个lock_guard对象时,获取提供给它的互斥锁的所有权,并自动调用互斥锁的lock()方法来加锁。如果互斥锁已经被其他线程锁定,当前线程将会阻塞,直到互斥锁被解锁。
当lock_guard对象离开作用域时,它的析构函数会被自动调用。在析构函数中,lock_guard会调用互斥锁的unlock()方法来解锁互斥锁。这样可以确保即使在异常情况下,互斥锁也能被正确解锁,避免死锁的发生。
lock_guard特点:
1、创建即加锁,作⽤域结束⾃动析构并解锁,⽆需⼿⼯解锁
2、不能中途解锁,必须等作⽤域结束才解锁
3、不能复制

#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx; // 定义一个互斥锁
int counter = 0; // 共享资源

void increment() {
    for (int i = 0; i < 100000; ++i) {
        std::lock_guard<std::mutex> lock(mtx); // 加锁
        ++counter; // 访问共享资源
    }
}
int main() {
    std::thread t1(increment);
    std::thread t2(increment);
    t1.join();
    t2.join();
    std::cout << "Counter: " << counter << std::endl;
    return 0;
}

互斥量Mutex:

/**
 * @brief 互斥量
 */
class Mutex : Noncopyable { //继承不可拷贝数据类
public: 
    /// 局部锁
    typedef ScopedLockImpl<Mutex> Lock;
    /**
     * @brief 构造函数
     */
    Mutex() {
        pthread_mutex_init(&m_mutex, nullptr);
    }
    /**
     * @brief 析构函数
     */
    ~Mutex() {
        pthread_mutex_destroy(&m_mutex);
    }
    /**
     * @brief 加锁
     */
    void lock() {
        pthread_mutex_lock(&m_mutex);
    }
    /**
     * @brief 解锁
     */
    void unlock() {
        pthread_mutex_unlock(&m_mutex);
    }
private:
    /// mutex
    pthread_mutex_t m_mutex;
};

读写互斥量RWMutex:

/**
 * @brief 读写互斥量
 */
class RWMutex : Noncopyable{
public:

    /// 局部读锁
    typedef ReadScopedLockImpl<RWMutex> ReadLock;

    /// 局部写锁
    typedef WriteScopedLockImpl<RWMutex> WriteLock;

    /**
     * @brief 构造函数
     */
    RWMutex() {
        pthread_rwlock_init(&m_lock, nullptr);
    }
    /**
     * @brief 析构函数
     */
    ~RWMutex() {
        pthread_rwlock_destroy(&m_lock);
    }
    /**
     * @brief 上读锁
     */
    void rdlock() {
        pthread_rwlock_rdlock(&m_lock);
    }
    /**
     * @brief 上写锁
     */
    void wrlock() {
        pthread_rwlock_wrlock(&m_lock);
    }
    /**
     * @brief 解锁
     */
    void unlock() {
        pthread_rwlock_unlock(&m_lock);
    }
private:
    /// 读写锁
    pthread_rwlock_t m_lock;
};

自旋锁

自旋锁是一种同步机制,它不会导致线程进入睡眠状态,而是通过循环不断尝试获取锁资源,可以避免线程切换的开销。但只适用于锁被持有时间短的场景,自旋等待的时间不会太长,而且可以避免线程切换的开销。如果锁被持有时间较长或竞争激烈导致很多线程在自旋等待,那么自旋锁可能会导致CPU资源的浪费,因为线程在等待时会持续占用CPU周期
此外,自旋锁不适合在中断处理中使用,因为中断处理程序应该尽快完成,避免长时间占用CPU。

/**
 * @brief 自旋锁
 */
class Spinlock : Noncopyable {
public:
    /// 局部锁
    typedef ScopedLockImpl<Spinlock> Lock;

    /**
     * @brief 构造函数
     */
    Spinlock() {
        pthread_spin_init(&m_mutex, 0);
    }

    /**
     * @brief 析构函数
     */
    ~Spinlock() {
        pthread_spin_destroy(&m_mutex);
    }

    /**
     * @brief 上锁
     */
    void lock() {
        pthread_spin_lock(&m_mutex);
    }

    /**
     * @brief 解锁
     */
    void unlock() {
        pthread_spin_unlock(&m_mutex);
    }
private:
    /// 自旋锁
    pthread_spinlock_t m_mutex;
};

原子锁??感觉就是用原子布尔类型实现的自旋锁

CASLock使用C++11中的std::atomic_flag来实现无锁同步,lock()函数使用std::atomic_flag_test_and_set_explicit()函数尝试获取锁,如果获取失败则一直循环等待。unlock()函数使用std::atomic_flag_clear_explicit()函数释放锁。
定义了一个volatile std::atomic_flag类型的成员变量m_mutex,用于表示锁的状态。由于它是volatile类型的,因此编译器不会对其进行优化,保证了其可见性,每次操作都会从内存读取m_mutex的值。

/**
 * @brief 原子锁
 */
class CASLock : Noncopyable {
public:
    /// 局部锁
    typedef ScopedLockImpl<CASLock> Lock;

    /**
     * @brief 构造函数
     */
    CASLock() {
        m_mutex.clear();
    }

    /**
     * @brief 析构函数
     */
    ~CASLock() {
    }

    /**
     * @brief 上锁
     */
    void lock() {
    	//获取失败则一直循环等待  感觉和自旋锁没区别了
        while (std::atomic_flag_test_and_set_explicit(&m_mutex, std::memory_order_acquire));
    }

    /**
     * @brief 解锁
     */
    void unlock() {
        std::atomic_flag_clear_explicit(&m_mutex, std::memory_order_release);
    }
private:
    /// 原子状态
    volatile std::atomic_flag m_mutex;
};

CASLock lock;
int shared_data = 0;

void thread_func() {
    for (int i = 0; i < 100000; ++i) {
        CASLock::Lock l(lock); // 加锁
        ++shared_data; // 访问共享资源
    }
}

int test2() {
    std::thread t1(thread_func);
    std::thread t2(thread_func);
    t1.join();
    t2.join();
    std::cout << "shared_data: " << shared_data << std::endl;
    return 0;
}

测试效果:
1、加原子锁
在这里插入图片描述
2、未加原子锁
在这里插入图片描述
#include 只能将基本数据类型声明为原子变量

#include<iostream>
#include<thread>
#include<windows.h>
#include<atomic>     //新增
#include<vector>
#include<mutex>        
using namespace std;
void Mythread(atomic<int>& num)    //修改
{
	for (int i = 0; i < 100000; i++)
	{		
		num++;	
	}
}
void test03()
{
	//int num = 0;
	std::atomic<int> num = 0;
	int threadNum = 2;     //线程个数
	vector<std::thread> m_thread;
	m_thread = vector<std::thread>(threadNum);
	for (auto i = 0; i < threadNum; i++)
	{
		m_thread[i] = std::thread(Mythread, &num);
	}
	for (auto i = 0; i < threadNum; i++)
	{
		m_thread[i].join();
	}
	cout <<"结果:"<<num << endl;
}
int main()
{
	test03();
	system("pause");
	return 0;
}

小结一下:

互斥量(Mutex):
互斥量是最基本的同步机制之一。它阻止多个线程同时访问共享资源。
当一个线程锁定互斥量时,如果另一个线程尝试锁定同一个互斥量,它将被阻塞(挂起),直到拥有锁的线程释放该锁。
互斥量适用于锁定时间较长的场景,比如复杂操作或涉及I/O的操作。
在 C++ 中,可以使用 头文件中的 std::mutex 类。
自旋锁(Spinlock):
自旋锁在等待锁释放时,线程会在循环检查锁的状态直到获取锁,它不会使线程挂起,而是占用CPU周期等待。
自旋锁适用于锁定时间非常短的场景,因为它避免了线程挂起和恢复的开销(避免线程切换)。
C++11 标准没有直接提供自旋锁,但可以通过原子操作实现,或者使用平台特定的实现(如 POSIX 的 pthread_spinlock,POSIX“可移植操作系统接口”,能够在多种系统之间使用)。
原子锁(Atomic Lock):
原子操作是指不可分割、不会被线程调度机制打断的操作。在执行完毕之前,不会有其他线程对这个操作进行干扰。
C++11 引入了原子操作库 ,提供了一组原子类型,如 std::atomic,可以用来实现低开销的线程安全操作。
原子操作通常用于简单的赋值、递增、递减等操作,而且是无锁的,所以开销比互斥量和自旋锁都要小。
总结:
使用互斥量时,长时间锁定资源会使其他线程挂起,适合复杂操作。
使用自旋锁时,线程会忙等待直到获取锁,适合短时间锁定资源。
使用原子操作时,可以保证单一操作的线程安全,无需锁定,开销最小,但仅限于简单操作(因为只有两种状态)。

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

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

相关文章

数仓建设实践——58用户画像数仓建设

目录 一、数据仓库&用户画像简介 1.1 数据仓库简介 1.2 数据仓库的价值 1.3 用户画像简介 1.4 用户画像—标签体系 二、用户画像数仓建设过程 2.1 画像数仓—背景&现状 2.2 画像数仓—整体架构 2.3 画像数仓—研发流程 2.4 画像数仓—指标定义 2.5 画像数仓…

Day50:WEB攻防-PHP应用文件包含LFIRFI伪协议编码算法无文件利用黑白盒

目录 文件包含-原理&分类&利用&修复 文件读取 文件写入 代码执行 远程利用思路 黑盒利用-VULWEB 白盒利用-CTFSHOW-伪协议玩法 78-php&http协议 79-data&http协议 80-81-日志包含 87-php://filter/write&加密编码 88-data&base64协议 …

【深度学习】【机器学习】用神经网络进行入侵检测,NSL-KDD数据集,基于机器学习(深度学习)判断网络入侵

文章目录 下载数据集NSL-KDD数据集介绍输入的41个特征输出的含义数据处理&&训练技巧建神经网络&#xff0c;输入41个特征&#xff0c;输出是那种类别的攻击模型训练模型推理写gradio前端界面&#xff0c;用户自己输入41个特征&#xff0c;后端用模型推理计算后显示出是…

银行卡的分类

银行卡是银行账户的一种体现形式&#xff0c;它是由银行机构发行的具有消费信用、转账结算、存取现金等全部或部分功能作为结算支付工具的各类卡的统称。 &#xff08;1&#xff09;按是否具有授信额度分类 ①借记卡&#xff1a;借记卡是指发卡银行向申请人签发的&#xff0c;没…

牛客NC79 丑数【中等 堆、优先级队列 Java,Go,PHP Go和PHP中我自己实现了优先级队列】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/6aa9e04fc3794f68acf8778237ba065b 思路 注意&#xff1a; 数据范围&#xff1a;0≤n≤2000&#xff0c; 2000肯定到不了&#xff0c;最多到1690&#xff0c;相同题目链接&#xff1a;https://www.lintcode.com…

netty构建udp服务器以及发送报文到客户端客户端详细案例

目录 一、基于netty创建udp服务端以及对应通道设置关键 二、发送数据 三、netty中的ChannelOption常用参数说明 1、ChannelOption.SO_BACKLOG 2、ChannelOption.SO_REUSEADDR 3、ChannelOption.SO_KEEPALIVE 4、ChannelOption.SO_SNDBUF和ChannelOption.SO_RCVBUF 5、Ch…

CUDA安装 Windows版

目录 一、说明 二、安装工具下载 三、CUDA安装 四、cuDNN配置 五、验证安装是否成功 一、说明 windows10 版本安装 CUDA &#xff0c;首先需要下载两个安装包 CUDA toolkitcuDNN 官方教程 CUDA&#xff1a;https://docs.nvidia.com/cuda/cuda-installation-guide-micro…

2.2 添加商户缓存

实战篇Redis 2.2 添加商户缓存 在我们查询商户信息时&#xff0c;我们是直接操作从数据库中去进行查询的&#xff0c;大致逻辑是这样&#xff0c;直接查询数据库那肯定慢咯&#xff0c;所以我们需要增加缓存 GetMapping("/{id}") public Result queryShopById(Pat…

政安晨:【深度学习神经网络基础】(一)—— 逐本溯源

政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: 政安晨的机器学习笔记 希望政安晨的博客能够对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff01; 与计算机一样的古老历史 神经网络的出现可追溯到20世纪40年…

Android源码阅读WorkMangaer - 6

前言 由于笔者目前水平限制&#xff0c;表达能力有限&#xff0c;尽请见谅。 WorkManager 是 Android Jetpack 库的一部分&#xff0c;提供了一种向后兼容的方式来安排可延迟的异步任务&#xff0c;这些任务即使在应用退出或设备重启后也应该继续执行&#xff0c;它是 Androi…

记录 AI绘图 Stable Diffusion的本地安装使用,可搭建画图服务端

开头 最近刷短视频看到了很多关于AI绘图&#xff0c;Midjourney&#xff0c;gittimg.ai&#xff0c;Stable Diffusion等一些绘图AI工具&#xff0c;感受到了AI绘画的魅力。通过chatGPT生成关键词再加上绘图工具&#xff0c;真是完美&#xff0c;文末教大家如何用gpt提词 Midj…

Anaconda的GEE环境中安装torch库

打开Anaconda&#xff0c;点击运行&#xff0c;打开terminal 输入pip install torch 而且由于anaconda中自己配置好了镜像源&#xff0c;在pip时自动使用清华镜像源

2024年4月份 风车IM即时通讯系统APP源码 版完整苹果安卓教程

关于风车IM&#xff0c;你在互联网上能随便下载到了基本都是残缺品&#xff0c; 经过我们不懈努力最终提供性价比最高&#xff0c;最完美的版本&#xff0c; 懂货的朋友可以直接下载该版本使用&#xff0c;经过严格测试&#xff0c;该版本基本完美无缺。 下载地址&#xff1a;…

【正点原子FreeRTOS学习笔记】————(4)FreeRTOS中断管理

这里写目录标题 一、什么是中断&#xff1f;&#xff08;了解&#xff09;二、中断优先级分组设置&#xff08;熟悉&#xff09;三、中断相关寄存器&#xff08;熟悉&#xff09;四、FreeRTOS中断管理实验&#xff08;掌握&#xff09; 一、什么是中断&#xff1f;&#xff08;…

华为数通 HCIP-Datacom H12-831 题库补充(3/27)

2024年 HCIP-Datacom&#xff08;H12-831&#xff09;最新题库&#xff0c;完整题库请扫描上方二维码&#xff0c;持续更新。 如图所示&#xff0c;关于R4路由器通过IS-IS计算出来的IPv6路由&#xff0c;哪一选项的描述是错误的&#xff1f; A&#xff1a;R4通过IS—IS只学习到…

【企业动态】吉利雷达汽车来访东胜物联,考察交流,洽谈车联网生态合作

近日&#xff0c;我们非常高兴接待吉利雷达汽车一行莅临东胜物联位于湖州市的生产工厂&#xff0c;进行参观考察&#xff0c;并就未来的合作展开深入商讨与交流。 雷达新能源汽车隶属于吉利控股集团&#xff0c;是一家专注于户外生态的中高端新能源智能汽车企业。雷达通过共享吉…

【启发式算法】同核分子优化算法 Homonuclear Molecules Optimization HMO算法【Matlab代码#70】

文章目录 【获取资源请见文章第4节&#xff1a;资源获取】1. 算法简介2. 部分代码展示3. 仿真结果展示4. 资源获取 【获取资源请见文章第4节&#xff1a;资源获取】 1. 算法简介 同核分子优化算法&#xff08;Homonuclear Molecules Optimization&#xff0c;HMO&#xff09;是…

网页版短信系统后台开发要点|短信平台软件开发搭建

在开发网页版短信系统的后台时&#xff0c;有一些关键要点需要注意&#xff0c;以确保系统的稳定性、安全性和高效性。以下是一些开发网页版短信系统后台时的重要要点&#xff1a; 用户管理&#xff1a;实现用户权限管理功能&#xff0c;包括用户注册、登录、角色分配等&#x…

Redis为什么快

引言 Redis是一个高性能的开源内存数据库,以其快速的读写速度和丰富的数据结构支持而闻名。作为一个轻量级、灵活的键值存储系统,Redis在各种应用场景下都展现出了惊人的性能优势。无论是作为缓存工具、会话管理组件、消息传递媒介,还是在实时数据处理任务和复杂的分布式系…

YOLOv9改进策略:卷积魔改 | SPD-Conv,低分辨率图像和小物体涨点明显

&#x1f4a1;&#x1f4a1;&#x1f4a1;本文改进内容&#xff1a;SPD-Conv由一个空间到深度(SPD)层和一个无卷积步长(Conv)层组成,特别是在处理低分辨率图像和小物体等更困难的任务时。 &#x1f4a1;&#x1f4a1;&#x1f4a1;SPD-Conv在多个数据集验证能够暴力涨点&#x…