700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > 【C++模块实现】| 【07】对于互斥 自旋锁 条件变量 信号量简介及封装

【C++模块实现】| 【07】对于互斥 自旋锁 条件变量 信号量简介及封装

时间:2022-12-05 03:05:42

相关推荐

【C++模块实现】| 【07】对于互斥 自旋锁 条件变量 信号量简介及封装

文章目录

索引一、简介1 线程互斥1.1 互斥锁1.2 读写锁1.3 自旋锁1.4 局部锁1.5 原子操作及原子锁1.6 线程安全的过度优化volatile2 线程通信2.1 信号量2.2 条件变量

========》以下全部代码查看《========

索引

【C++模块实现】| 【01】日志系统实现

【C++模块实现】| 【02】日志系统优化

【C++模块实现】| 【03】文件管理模块

【C++模块实现】| 【04】配置模块

【C++模块实现】| 【05】日志模块增加配置模块的功能

【C++模块实现】| 【06】日志模块添加循环覆盖写文件功能

【C++模块实现】| 【07】对于互斥、自旋锁、条件变量、信号量简介及封装

【C++模块实现】| 【08】循环覆盖写内存缓冲区(日志中多线程记录)

【C++模块实现】| 【09】线程模块及线程池的实现

【C++模块实现】| 【10】定时器的实现

【C++模块实现】| 【11】封装Ipv4、Ipv6、unix网络地址

该模块是从sylar服务器框架中学习的,以下将会对其进行总结以加深对该框架的理解;以下是线程同步的内容:线程通信:信号量、条件变量;线程互斥:锁

========》视频地址《========

一、简介

========》互斥量、信号量、条件变量的基本使用及参考代码《=======

========》Linux【线程】 | 【01】线程、线程同步、线程安全《========

// 以下对锁的封装能够使用类的构造函数来加锁,析构函数进行释放锁;sylar::Mutex s_mutes;{sylar::Mutex::Lock lock(s_mutes);// 加锁操作...// 当离开该作用域时,该lock会被回收,由于它时自动变量,此时即调用析构函数,执行是方法锁的操作;// 该方法简化了锁的使用方法,不需要手动释放锁,避免出现死锁的现象;}

1 线程互斥

1.1 互斥锁

互斥锁Mutex是一种用于多线程编程中,防止两条线程同时对同一公共资源进行读写的机制; 该目的通过将代码切片成一个一个的临界区域(critical section)达成;临界区域指的是一块对公共资源进行存取的代码,并非一种机制或是算法;一个程序、进程、线程可以拥有多个临界区域,但是并不一定会应用互斥锁;被哪个线程获取就需要哪个线程将它释放;

flag、队列、计数器、中断处理程序等用于在多条并行运行的代码间传递数据、同步状态等的资源。维护这些资源的同步、一致和完

整是很困难的,因为一条线程可能在任何一个时刻被暂停(休眠)或者恢复(唤醒);

使用场景

临界区有IO操作;临界区代码复杂或者循环量大;临界区竞争非常激烈单核处理器

// 声明一个互斥量 pthread_mutex_t mtx;// 初始化 pthread_mutex_init(&mtx, NULL);// 加锁 pthread_mutex_lock(&mtx);// 解锁 pthread_mutex_unlock(&mtx);// 销毁pthread_mutex_destroy(&mtx);

/*** @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);}pthread_mutex_t getMutex() const {return m_mutex; }private:/// mutexpthread_mutex_t m_mutex;};

1.2 读写锁

读写锁是计算机程序的并发控制的一种同步机制,用于解决读写问题。读操作可并发重入,写操作是互斥的;- 读写锁通常用互斥锁、条件变量、信号量实现;【读写锁可以有不同的操作模式优先级】- 读操作优先:允许最大并发,但写操作可能饿死;- 写操作优先:一旦所有已经开始的读操作完成,等待的写操作立即获得锁。内部实现需要两把互斥锁;- 未指定优先级

/*** @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;};

1.3 自旋锁

自旋锁是计算机科学用于多线程同步的一种锁,线程反复检查锁变量是否可用。由于线程在这一过程中保持执行,因此是一种忙等待。一旦获取了自旋锁,线程会一直保持该锁,直至显式释放自旋锁;- 自旋锁避免了进程上下文的调度开销,因此对于线程只会阻塞很短时间的场合是有效的;- 自旋锁用于处理器之间的互斥,适合保护很短的临界区,并且不允许在临界区睡眠。申请自旋锁的时候,如果自旋锁被其他处理器占有,本处理器自旋等待(也称为忙等待);因此操作系统的实现在很多地方往往用自旋锁;- 单核CPU不适于使用自旋锁,这里的单核CPU指的是单核单线程的CPU,因为,在同一时间只有一个线程是处在运行状态,假设运行线程A发现无法获取锁,只能等待解锁,但因为A自身不挂起,所以那个持有锁的线程B没有办法进入运行状态,只能等到操作系统分给A的时间片用完,才能有机会被调度。这种情况下使用自旋锁的代价很高;获取、释放自旋锁,实际上是读写自旋锁的存储内存或寄存器。因此这种读写操作必须是原子的;通常用test-and-set等原子操作来实现;【优点】:- 自旋锁不会使线程状态发生切换,一直处于用户态,即线程一直都是active的;不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快。- 非自旋锁在获取不到锁的时候会进入阻塞状态,从而进入内核态,当获取到锁的时候需要从内核态恢复,需要线程上下文切换。(线程被阻塞后便进入内核(Linux)调度状态,这个会导致系统在用户态与内核态之间来回切换,严重影响锁的性能)。

使用场景

临界区持锁时间非常短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;};

1.4 局部锁

/*** @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:/// mutexT& m_mutex;/// 是否已上锁bool m_locked;};/*** @brief 局部读锁模板实现*/template<class T>struct ReadScopedLockImpl {public:/*** @brief 构造函数* @param[in] mutex 读写锁*/ReadScopedLockImpl(T& mutex):m_mutex(mutex) {m_mutex.rdlock();m_locked = true;}/*** @brief 析构函数,自动释放锁*/~ReadScopedLockImpl() {unlock();}/*** @brief 上读锁*/void lock() {if(!m_locked) {m_mutex.rdlock();m_locked = true;}}/*** @brief 释放锁*/void unlock() {if(m_locked) {m_mutex.unlock();m_locked = false;}}private:/// mutexT& m_mutex;/// 是否已上锁bool m_locked;};/*** @brief 局部写锁模板实现*/template<class T>struct WriteScopedLockImpl {public:/*** @brief 构造函数* @param[in] mutex 读写锁*/WriteScopedLockImpl(T& mutex):m_mutex(mutex) {m_mutex.wrlock();m_locked = true;}/*** @brief 析构函数*/~WriteScopedLockImpl() {unlock();}/*** @brief 上写锁*/void lock() {if(!m_locked) {m_mutex.wrlock();m_locked = true;}}/*** @brief 解锁*/void unlock() {if(m_locked) {m_mutex.unlock();m_locked = false;}}private:/// MutexT& m_mutex;/// 是否已上锁bool m_locked;};

1.5 原子操作及原子锁

一般对于简单的变量使用,使用原子锁即可;其内部是在硬件层实现的锁机制,让当前指令没有执行完,是不会让其他线程执行;

=======》原子类型与原子操作《=======

/*** @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;};

1.6 线程安全的过度优化volatile

【例1】:存放到寄存器不写回

x = 0;Thread1Thread2lock();lock();x++;x++;unlock();unlock();- 使用lock对x进行保护,不被破坏,则执行后应该x = 2;- 但是当编译器为了提高对变量访问速度,将他放入寄存器中,然而在不同线程的寄存器是相互独立;【执行步骤】:若thread1先获取锁- Thread1读取x,将x保存在某个寄存器中R[1] = 0;- R[1]++,由于后续可能对x进行访问,暂时先不将R[1]写回x;- Thread2读取x值到某个寄存器R[2]=0;- R[2]++;- thread2将R[2]写回到x中;- Thread1将R[1]写回至x中;

【例2】:执行顺序被交换

x = y = 0;Thread1Thread2x = 1;y = 1;r1 = y;r2 = x;- 通过上面的程序,正常逻辑r1和r2至少有一个为1,不可能同时为0;- 但编译器为了提高效率,有可能会交换指令的顺序,将毫不相干的两条指令执行顺序交换;===> 上述代码可能变成:x = y = 0;Thread1Thread2r1 = y;r2 = x;x = 1;y = 1;

通过上面的例子,我们需要避免出现上述状况,可以使用volatile来避免编译器过度优化:

阻止编译器为了提高速度将一个变量缓存到寄存器内而不写回;阻止编译器调整操作volatile变量的指令顺序(但无法结技CPU动态调度换序);

double check

volatile T* pInst = 0;T* GetInstance() {if(pInst == NULL) {lock();if(pInst == NULL) {pInst = new T;}unlock();}return pInst;}

这段代码在逻辑上是没有问题的,但函数返回时,pInst总是指向一个有效的对象;

【CPU的乱序问题,C++的new包含两个步骤及pInst = new T】:

分配内存;在内存的位置上调用构造函数;将内存的地址赋值给pInst;上述中2、3步骤顺序可能会被颠倒,可能出现pInst不为NULL但对象没有构造完成时遇到另外一个并发调用,而进入判断则会为false,所有直接返回pInst(尚未完成构造);

【解决方法】:

提供barrier指令,该指令会阻止CPU将该指令之前的指令交换到barrier后;

2 线程通信

2.1 信号量

========》使用互斥锁及条件变量替代信号量《========

/*** @brief 信号量*/class Semaphore : Noncopyable {public:/*** @brief 构造函数* @param[in] count 信号量值的大小*/Semaphore(uint32_t count = 0);/*** @brief 析构函数*/~Semaphore();/*** @brief 获取信号量*/void wait();/*** @brief 释放信号量*/void notify();private:sem_t m_semaphore;};/** 信号量封装使用条件变量和锁 */class OwnSemaphore : Noncopyable {public:typedef Mutex MutexType;OwnSemaphore(size_t count=0);~OwnSemaphore();void wait();void notify();size_t getCount() const {return m_count; }void reset() {m_count = 0;}private:size_t m_count;MutexType m_mutex;Cond m_cond;};Semaphore::Semaphore(uint32_t count) {if(sem_init(&m_semaphore, 0, count)) {throw std::logic_error("sem_init error");}}Semaphore::~Semaphore() {sem_destroy(&m_semaphore);}void Semaphore::wait() {if(sem_wait(&m_semaphore)) {throw std::logic_error("sem_wait error");}}void Semaphore::notify() {if(sem_post(&m_semaphore)) {throw std::logic_error("sem_post error");}}OwnSemaphore::OwnSemaphore(size_t count):m_count(count){}OwnSemaphore::~OwnSemaphore() {}void OwnSemaphore::wait() {MutexType::Lock lock(m_mutex);while (m_count == 0) {m_cond.wait(m_mutex.getMutex());}--m_count;}void OwnSemaphore::notify() {m_count++;m_cond.signal();}

2.2 条件变量

多线程中,各个线程都是随着OS的调度算法,占用CPU时间片来执行指令,每个线程的运行完全没有顺序。但是在某些应用场景下,一个线程需要等待另外一个线程的运行结果,才能继续往下执行;

pthread_cond_wait:需要传入一个互斥锁,在内部被释放,就被会进入等待;pthread_cond_signal:会发生信号,wait的线程从等待比变为阻塞状态;常见生产者、消费者模型;

/** 封装条件变量 */class Cond {public:Cond();void wait(pthread_mutex_t mutex);void signal();~Cond();private:pthread_cond_t m_cond;};Cond::Cond() {pthread_cond_init(&m_cond, NULL);}void Cond::wait(pthread_mutex_t mutex) {pthread_cond_wait(&m_cond, &mutex);}void Cond::signal() {pthread_cond_signal(&m_cond);}Cond::~Cond() {pthread_cond_destroy(&m_cond);}

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。