当前位置: 首页 > article >正文

C++ 线程安全之互斥锁

目录

线程安全

1. 原子性

2. 可见性

3. 顺序性

  互斥锁

1.mutex类

2.timed_mutex类

3.recursive_mutex类

4.lock_guard类


线程安全

  线程安全是多线程编程是的计算机程序代码中的一个概念。在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且准确的执行,不会出现数据污染等意外情况。上述是百度百科给出的一个概念解释。换言之,线程安全就是某个函数在并发环境中调用时,能够处理好多个线程之间的共享变量,是程序能够正确执行完毕。也就是说我们想要确保在多线程访问的时候,我们的程序还能够按照我们的预期的行为去执行,那么就是线程安全了。

要考虑线程安全问题,就需要先考虑并发的三大基本特性原子性、可见性以及顺序性。

1. 原子性

原子性是指在一个操作中就是cpu不可以在中途暂停然后再调度,即不被中断操作,要不全部执行完成,要不都不执行。就好比转账,从账户A向账户B转1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元。2个操作必须全部完成。

那程序中原子性指的是最小的操作单元,比如自增操作,它本身其实并不是原子性操作,分了3步的,包括读取变量的原始值、进行加1操作、写入工作内存。所以在多线程中,有可能一个线程还没自增完,可能才执行到第二部,另一个线程就已经读取了值,导致结果错误。那如果我们能保证自增操作是一个原子性的操作,那么就能保证其他线程读取到的一定是自增后的数据。

2. 可见性

当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

若两个线程在不同的cpu,那么线程1改变了i的值还没刷新到主存,线程2又使用了i,那么这个i值肯定还是之前的,线程1对变量的修改线程没看到,这就是可见性问题。

3. 顺序性

程序执行的顺序按照代码的先后顺序执行,CPU为了整体的执行效率,可能会优化代码的执行顺序,但会确保最终执行结果与不优化的执行结果一致.

例如:  int i=10; i=20;i+=20;i=60;  会自动优化为int i = 60;

可通过在变量前加上volatile关键字使变量具有可见性,并禁止CPU进行代码优化(重排序).

代码示例(非线程安全情况):

#include <iostream>

#include <thread>        // 线程类头文件。

using namespace std;



int aa = 0;     // 定义全局变量。



// 普通函数,把全局变量aa加1000000次。

void func() {

for (int ii = 1; ii <= 1000000; ii++)

aa++;

}



int main()

{

// 用普通函数创建线程。

thread t1(func);     // 创建线程t1,把全局变量aa加1000000次。

thread t2(func);     // 创建线程t2,把全局变量aa加1000000次。



t1.join();         // 回收线程t1的资源。

t2.join();         // 回收线程t2的资源。



cout << "aa=" << aa << endl;   // 显示全局变量aa的值。

}

  互斥锁

C++11提供了四种互斥锁:

  1. mutex:互斥锁。
  2. timed_mutex:带超时机制的互斥锁。
  3. recursive_mutex:递归互斥锁。
  4. recursive_timed_mutex:带超时机制的递归互斥锁。

包含头文件:#include <mutex>

1.mutex

1)加锁lock()

互斥锁有锁定和未锁定两种状态。

如果互斥锁是未锁定状态,调用lock()成员函数的线程会得到互斥锁的所有权,并将其上锁。

如果互斥锁是锁定状态,调用lock()成员函数的线程就会阻塞等待,直到互斥锁变成未锁定状态。

2)解锁unlock()

只有持有锁的线程才能解锁。

3)尝试加锁try_lock()

如果互斥锁是未锁定状态,则加锁成功,函数返回true。

如果互斥锁是锁定状态,则加锁失败,函数立即返回false。(线程不会阻塞等待)

代码示例(cout也是全局对象,为屏幕输出流对象):

#include <iostream>

#include <thread>                // 线程类头文件。

#include <mutex>                // 互斥锁类的头文件。

using namespace std;



mutex mtx;        // 创建互斥锁,保护共享资源cout对象。



// 普通函数。

void func(int bh, const string& str) {

for (int ii = 1; ii <= 10; ii++)

{

mtx.lock();      // 申请加锁。

cout << "第" << ii << "次表白:亲爱的" << bh << "号," << str << endl;

mtx.unlock();  // 解锁。

this_thread::sleep_for(chrono::seconds(1));     // 休眠1秒。

}

}



int main()

{

// 用普通函数创建线程。

thread t1(func, 1, "我是星穹铁道高手。");

thread t2(func, 2, "我是星穹铁道高手。");

thread t3(func, 3, "我是星穹铁道高手。");

thread t4(func, 4, "我是星穹铁道高手。");

thread t5(func, 5, "我是星穹铁道高手。");



t1.join();         // 回收线程t1的资源。

t2.join();         // 回收线程t2的资源。

t3.join();         // 回收线程t3的资源。

t4.join();         // 回收线程t4的资源。

t5.join();         // 回收线程t5的资源。

}

注:无论什么锁,加锁段内的代码在确保线程安全的情况下应当尽量少,越少效率越高,且确保解锁后至下一次加锁前或线程结束前的程序是线程安全的.

2.timed_mutex

增加了两个成员函数(与try_lock类似):

bool try_lock_for(时间长度);

bool try_lock_until(时间点);

时间类概念详见之前文章<<C++时间操作chrono库>> 

3.recursive_mutex

递归互斥锁允许同一线程多次获得互斥锁,可以解决同一线程多次加锁造成的死锁问题。

代码示例:

#include <iostream>

#include <mutex>        // 互斥锁类的头文件。

using namespace std;



class AA

{

recursive_mutex m_mutex;

public:

void func1() {

m_mutex.lock();

cout << "调用了func1()\n";

m_mutex.unlock();

}



void func2() {

m_mutex.lock();

cout << "调用了func2()\n";

func1();

m_mutex.unlock();

}

};



int main()

{

AA aa;

//aa.func1();

aa.func2();

}

4.lock_guard

lock_guard是模板类,可以简化互斥锁的使用,也更安全。

lock_guard的定义如下:

template<class Mutex>

class lock_guard

{

    explicit lock_guard(Mutex& mtx);

}

lock_guard在构造函数中加锁,在析构函数中解锁。

还有个unique_lock<mutex> 用于把互斥锁转换为unique_lock类,本质与之前的mutex互斥锁毫无区别,但是增加了 unlock()成员函数,用于主动解锁,不用像lock_guard类需等待超出作用域后自动调用析构函数解锁(unique_lock类超出作用域后也会自动调用析构函数解锁,手动加{}可以定义作用域范围)

lock_guard用了RAII思想(在类构造函数中分配资源,在析构函数中释放资源,保证资源在离开作用域时自动释放).智能指针也是采用的RAll思想.   

注:此课件及源代码来自B站up主:码农论坛,该文章仅作为本人学习笔记及交流学习使用,本人仅做整理并补充学习理解和知识点。 


http://www.kler.cn/a/517373.html

相关文章:

  • K8S中的数据存储之基本存储
  • 新电脑安装系统找不到硬盘原因和解决方法来了
  • 蓝桥杯之c++入门(一)【第一个c++程序】
  • 【高项】6.3 排列活动顺序 ITTO
  • CSS 提示工具:优化网页设计,提升用户体验
  • GIS 中的 SQLAlchemy:空间数据与数据库之间的桥梁
  • 《机器学习数学基础》补充资料:超平面
  • 【Unity3D】《跳舞的线》游戏的方块单方向拉伸实现案例
  • 关于hexo-deploy时Spawn-Failed的几种解决方案
  • Mysql面试题----什么是垂直分表、垂直分库、水平分库、水平分表
  • 【华为OD-E卷 - 计算网络信号 100分(python、java、c++、js、c)】
  • 「 机器人 」扑翼飞行器控制方法浅谈
  • Go的垃圾回收(GC)机制
  • 如何在 Spring Boot 中实现自定义属性
  • 计算机视觉算法实战——驾驶员安全带检测
  • 2022年全国职业院校技能大赛网络系统管理赛项模块A:网络构建(样题8)
  • 深入理解 HTML DOM:文档对象模型详解
  • windows系统改变vscode的插件位置
  • 【Bug 记录】el-sub-menu 第一次进入默认不高亮
  • 【17】组织测试(一)
  • 组件封装-List
  • kettle与Springboot的集成方法,完整支持大数据组件
  • PySide(PyQT)进行SQLite数据库编辑和前端展示的基本操作
  • 使用 Git LFS 管理大文件基本简介
  • Java开发的商城系统怎样
  • Consul持久化配置报错1067---consul_start