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

19.6、C++11新特性有哪些⑥【并发】

C++11新特性有哪些

  • 并发
    • 线程(thread)
    • 原子类型
    • 互斥量(Mutex)
    • 锁(lock)

并发

线程(thread)

在这里插入图片描述

  • 创建线程对象后,必须要提供线程关联函数。两者都聚齐了,才能启动线程。线程函数一般情况下可按照以下四种方式提供:
    • 函数指针
    • lambda表达式
    • 函数对象
    • 包装器(function)
void ThreadFunc(int a) {
	cout << "Thread1" << a << endl;
}

void T()
{
	cout << "hello" << endl;
}

class TF
{
public:
	void TT()
	{
		cout << "NI" << endl;
	}
	void operator()()
	{
		cout << "Thread3" << endl;
	}
};

int main()
{
	// 线程函数为函数指针
	thread t1(ThreadFunc, 10);

	// 线程函数为lambda表达式
	thread t2([] {cout << "Thread2" << endl; });

	// 线程函数为函数对象
	TF tf;
	thread t3(tf);
	thread t5(&TF::TT, TF());

	//线程函数为包装器
	function<void()> t = T;
	thread t4(t);

	t1.join();
	t2.join();
	t3.join();
	t4.join();
	t5.join();
	cout << "Main thread!" << endl;
	return 0;
}
  • 禁止拷贝线程对象:thread类 不允许拷贝构造以及赋值,但是可以移动构造和移动赋值,即一个线程对象关联的 线程的状态 转移给 其他线程对象,转移期间不影响线程的执行

  • 线程函数参数

    • 线程函数的参数是以 值拷贝 的方式 拷贝到线程栈空间中的,实际引用的是 线程栈中的拷贝,而不是外部实参。所以,即使参数是引用类型,最后也不能把改变后的结果带到外面
    • 注意:如果线程参数是类成员函数时,必须将this作为线程函数参数
  • 可以通过 joinable()函数 判断线程是否是有效的,如果是以下任意情况,则线程无效:

    • 线程对象没有关联函数
    • 线程对象的状态已经转移给其他线程对象
    • 线程已经调用join 或者detach结束

原子类型

  • 声明一个类型为T 的原子类型变量t:
atmoic<T> t;    // 声明一个类型为T的原子类型变量t
  • 特点:

    • 如果是对原子类型的变量进行修改,就不需要加锁了。线程会自动地互斥访问原子类型

    • 原子类型是不允许拷贝和赋值的,因为atmoic模板类中的拷贝构造、移动构造、赋值运算符重载都被删除掉了

#include <atomic>
int main()
{
	atomic<int> a1(0);
	//atomic<int> a2(a1);   // 编译失败
	atomic<int> a2(0);
	//a2 = a1;               // 编译失败
	return 0;
}
  • 实例:

    该例中,使用原子变量后 就不再需要加锁了

    在这里插入图片描述

互斥量(Mutex)

原子类型可以保证一个变量的安全性,但是对于一段代码的安全性,我们只能通过 互斥量和锁 来实现

  • 普通互斥量(std:: mutex)

    在这里插入图片描述

    • 注意,线程函数调用 lock() 时,可能会发生以下三种情况:

      • 如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用 unlock之前,该线程一直拥有该锁
      • 如果当前互斥量被其他线程锁住,则当前的调用线程被阻塞住
      • 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)
    • 线程函数调用 try_lock() 时,可能会发生以下三种情况:

      • 如果当前互斥量没有被其他线程占有,则该线程锁住互斥量,直到该线程调用 unlock释放互斥量
      • 如果当前互斥量被其他线程锁住,则当前调用线程返回 false,而并不会被阻塞掉
      • 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)
  • 递归互斥量(std:: recursive_mutex)

    • 递归互斥锁 允许同一个线程对互斥量多次上锁(即递归上锁),来获得对互斥量对象的多层所有权。释放互斥量时需要调用相同次数的 unlock()。除此之外,std::recursive_mutex 的特性和 std::mutex 大致相同

    • 这对于递归函数 可能需要在同一线程中多次获取锁的情况很有用:

#include <iostream>
#include <mutex>
#include <thread>

std::recursive_mutex myRecursiveMutex;

void recursiveAccess(int depth) {
    std::unique_lock<std::recursive_mutex> lock(myRecursiveMutex);
    if (depth > 0) {
        recursiveAccess(depth - 1);
    }
    // 访问共享资源的代码
    std::cout << "Accessing shared resource at depth " << depth << "...\n";
}

int main() {
    std::thread t1(recursiveAccess, 3);

    t1.join();

    return 0;
}
  • 定时互斥量(std::timed_mutex)

  • 比 std::mutex 多了两个成员函数,try_lock_for(),try_lock_until() :

    • try_lock_for():函数参数表示一个时间范围,在这一段时间范围之内线程如果没有获得锁 则保持阻塞;如果在此期间其他线程释放了锁,则该线程可获得该互斥锁;如果超时(指定时间范围内没有获得锁),则函数调用返回false。
 timed_mutex myMutex;
    chrono::milliseconds timeout(100);  //100毫秒
    if (myMutex.try_lock_for(timeout))
    {
    	//在100毫秒内获取了锁
    	//业务代码
    	myMutex.unlock();  //释放锁
    }
    else
    {
    	//在100毫秒内没有获取锁
    	//业务代码
    }
  • try_lock_until():函数参数表示一个时刻,在这一时刻之前线程如果没有获得锁则保持阻塞;如果在此时刻前其他线程释放了锁,则该线程可获得该互斥锁;如果超过指定时刻没有获得锁,则函数调用返回false。

  • 定时递归互斥量(std::recursive_timed_mutex)

    允许同一线程多次获取锁,并提供了超时功能。与std::timed_mutex一样,std::recursive_timed_mutex也提供了try_lock_for()和try_lock_until()方法

锁(lock)

这里主要介绍两种RAII方式的锁封装(std::lock_guard和std::unique_lock),可以动态的释放锁资源,防止线程由于编码失误导致一直持有锁。

  • std::lock_gurad 是 C++11 中定义的模板类,取代了mutex的lock和unlock函数。定义如下:

    在这里插入图片描述

    • lock_guard类模板通过RAII来封装。在需要加锁的地方,只需要用互斥量实例化一个lock_guard对象,此时类内调用构造函数成功上锁;出作用域前,lock_guard对象要被销毁,调用析构函数自动解锁,可以有效避免死锁问题。
    • lock_guard对象之间是不能拷贝和赋值的
    • lock_guard的缺陷:太单一,用户没有办法对该锁进行控制,因此C++11又提供了unique_lock。
  • 独占锁(unique_lock)

与lock_guard不同的是,unique_lock更加的灵活(但效率上差一点,内存占用多一点),提供了更多的成员函数:

  • 上锁/解锁操作:lock、try_lock、try_lock_for、try_lock_until和unlock
  //1.立即上锁
  std::mutex mtx;
  std::unique_lock<std::mutex> lck(mtx);
  
  //2.延迟上锁
  std::unique_lock<std::mutex> lck(mtx, std::defer_lock); //只是创建对象,不上锁
  lck.lock(); // 此时才上锁
  
  if(lck.try_lock()){
      // 已获得锁
  }
  
  lck.unlock();
  • 修改操作:移动赋值、交换即swap(与另一个unique_lock对象互换所管理的互斥量所有权)、释放即release(返回它所管理的互斥量对象的指针,并释放所有权)
std::unique_lock<std::mutex> ul1(mtx);
  std::unique_lock<std::mutex> ul2 = std::move(ul1); // 把ul1的锁转移到ul2上,即现在是ul1对mtx上锁
  if (!ul1.owns_lock()) // 现在 ul1 应该不再持有锁了
  {
      assert(true);
  }
  • 获取属性:owns_lock(返回当前对象是否上了锁)、operator bool()(与owns_lock()的功能相同)、mutex(返回当前unique_lock所管理的互斥量的指针)
  if(lck.owns_lock()){
      // 此时拥有锁
  }
  
  auto* m = lck.mutex();

相关文章:

  • Elasticsearch:驾驭数据浪潮,利用Java API与Elasticsearch DSL构建智能搜索
  • DataWorks (数据工厂)介绍
  • 【word】电子签名设置、保存和调用
  • 【含文档+PPT+源码】基于SpringBoot电脑DIY装机教程网站的设计与实现
  • QT实现简约美观的动画Checkbox
  • 深入理解Linux内存缓存:提升性能的关键
  • 每日一题-奶酪题(蓝桥杯)【模拟】
  • LeeCode题库第四十一题
  • 《白帽子讲 Web 安全》之深入同源策略(万字详解)
  • 数字内容体验个性化推荐的核心优势是什么?
  • 力扣203.移除链表元素
  • iOS应用手动脱壳砸壳教程
  • 代码随想录算法训练营第三十一天 | 56. 合并区间 738.单调递增的数字
  • linux下自旋锁(spin_lock)
  • 回归算法模型总结
  • unity pico开发 四 物体交互 抓取 交互层级
  • 芯麦GC1262E:电脑散热风扇驱动芯片的优质之选并可替代传统的APX9262S茂达芯片
  • OFD签章技术和情景案例
  • M系列芯片 MacOS 在 Conda 环境中安装 TensorFlow 2 和 Keras 3 完整指南
  • 【C++】stack和queue以及priority_queue的使用以及模拟实现
  • 韩国代总统、国务总理韩德洙宣布辞职,将择期宣布参选总统
  • 49:49白热化,美参议院对新关税政策产生巨大分歧
  • 居委业委居民群策群力,7位一级演员来到上海一小区唱戏
  • 五大国货美妆去年业绩分化:珀莱雅百亿营收领跑,上海家化转亏
  • 国务院食安办:加强五一假期食品生产、销售、餐饮服务环节监管
  • 全国人民代表大会常务委员会公告〔十四届〕第十号