文章目录

  • 索引
    • 一、简介
      • 1 线程互斥
        • 1.1 互斥锁
        • 1.2 读写锁
        • 1.3 自旋锁
        • 1.4 局部锁
        • 1.5 原子操作及原子锁
        • 1.6 线程安全的过度优化volatile
      • 2 线程通信
        • 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;
Thread1     Thread2
lock();     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;
Thread1     Thread2
x = 1;     y = 1;
r1 = y;        r2 = x;
- 通过上面的程序,正常逻辑r1和r2至少有一个为1,不可能同时为0;
- 但编译器为了提高效率,有可能会交换指令的顺序,将毫不相干的两条指令执行顺序交换;
===> 上述代码可能变成:
x = y = 0;
Thread1     Thread2
r1 = 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);
}

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

  1. c++ 互斥量和条件变量

    线程同步时会遇到互斥量和条件变量配合使用的情况,下面看一下C++版的. test.h #include <pthread.h> #include <iostream>class ...

  2. liunx内核中的互斥自旋锁和读写自旋锁的实现详解

    今天把这两个锁的内核实现源码重新捋了一遍,基于liunx2,6.0,直接粘注释版: 核心文件,x86下实现的spinlock #ifndef __ASM_SPINLOCK_H #define __AS ...

  3. C++11学习笔记-----互斥量以及条件变量的使用

    在多线程环境中,当多个线程同时访问共享资源时,由于操作系统CPU调度的缘故,经常会出现一个线程执行到一半突然切换到另一个线程的情况.以多个线程同时对一个共享变量做加法运算为例,自增的汇编指令大致如下, ...

  4. 互斥量、条件变量与pthread_cond_wait()函数的使用,详解(二)

    互斥量.条件变量与pthread_cond_wait()函数的使用,详解(二) 1.Linux"线程" 进程与线程之间是有区别的,不过linux内核只提供了轻量进程的支持,未实现线 ...

  5. 关于互斥锁,条件变量的内核源码解析

    一.解决问题和适用范围 主要是用来等待一个条件,这个条件可能需要另一个线程来满足这个条件.这个和我们平常适用的pthread_mutex_lock的最大不同在于后者保护的一般是一个代码段(也就是关键区 ...

  6. 信号量 互斥锁 条件变量的区别

    信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作(大家都在semtake的时候,就阻塞在哪里).而互斥锁是用在多线程多任务互斥的,一个线程占用了某 ...

  7. Linux下互斥量与条件变量详细解析

    1. 首先pthread_cond_wait 的定义是这样的 The pthread_cond_wait() and pthread_cond_timedwait() functions are us ...

  8. 信号灯文件锁linux线程,linux——线程同步(互斥量、条件变量、信号灯、文件锁)...

    一.说明 linux的线程同步涉及: 1.互斥量 2.条件变量 3.信号灯 4.文件读写锁 信号灯很多时候被称为信号量,但个人仍觉得叫做信号灯比较好,因为可以与"SYSTEM V IPC的信 ...

  9. 并发编程(一): POSIX 使用互斥量和条件变量实现生产者/消费者问题

    boost的mutex,condition_variable非常好用.但是在Linux上,boost实际上做的是对pthread_mutex_t和pthread_cond_t的一系列的封装.因此通过对 ...

  10. 一个简单的互斥量与条件变量例子

    #include <pthread.h> #include <stdio.h> #include <stdlib.h> //互斥变量和条件变量静态初始化 pthre ...

最新文章

  1. hdu A + B Problem II(大数相加,数组实现)
  2. 2020人工神经网络第一次作业-参考答案第六部分
  3. 记一次php项目上线遇到的坑
  4. java参数传入泛型类型_Java 5.0 泛型之 使用泛型统一传入的参数类型
  5. vue项目打包部署linux_Vue项目打包部署到Nginx服务器
  6. CleanMyMac教程轻松解决各种使用难题
  7. 空间注意力机制sam_自己挖坑自己填,谷歌大改Transformer注意力,速度、内存利用率都提上去了...
  8. 创建maven的web项目,并用jetty调试
  9. 使用wget命令镜像网站
  10. 大数据项目流程(必须会)
  11. i510300h和i78750h参数对比哪个好
  12. 图像原点矩、二阶中心矩物理意义推导
  13. 绝地求生大逃杀常用英语
  14. HDU 5810(伯努利实验、多校7、规律)
  15. 判断用户flash是否安装了flash以及flash的版本
  16. 通达oa wbupload.php,通达 OA 代码审计篇二 :11.8 后台 Getshell
  17. 《和声学教程》学习笔记(六):下属七和弦SII7、导七和弦DVII7和属九和弦D9
  18. 从信息学奥赛获奖年级分布看信息学奥赛最佳学习线路
  19. 曲线曲面的基本理论2之曲线曲面表示方法
  20. 表格太长如何打印?一个小功能,轻松就搞定!!

热门文章

  1. 在谷歌chrome、Firefox等浏览器打开、编辑、保存微软Office、金山WPS文档
  2. 课程设计—通讯录管理系统
  3. java中word转pdf实现
  4. open 读Txt文件
  5. (转)wxWindows一些网文
  6. vmware服务器系统配置ip地址,教程 - Vmware ESXi IP地址配置
  7. 吴氏网解析——dips与极坐标系平面投影
  8. TortoiseSVN 官网 中文语言包位置
  9. 数学建模常用模型和算法介绍
  10. 《数学建模算法与应用》第2版 司守奎 孙兆亮及其习题解答两本书的配套程序及数据