C++并发与异步知识点最全汇总

2023-11-12

c++并发

1. thread

只能join()/detach()一次

#include<thread>

void some_func(int param) {

}

int main () {
	std::thread thread{somfunc, 10};
	// std::join(); 主线程(main)等待子线程执行完再继续执行
	
	// std::detach(); 主线程和子线程彻底分离, 子线程交给c++ runtime去回收资源	
}

2. this_thread命名空间

#include<thread>

std::this_thread::get_id();
std::this_thread::sleep_for(std::chrono::seconds(1));

auto time_point = std::chrono::steady_clock::now() + std::chrono::seconds(3);
std::this_thread::sleep_until(time_point);

3. 互斥

1. mutex

#include<mutex>
#include<iostream>

std::mutex mtx;

void out_count(int count) {
	mtx.lock();
    std::cout << count << std::endl;
    mtx.unglock();
}

2. 符合RAII标准的锁: lock_guard

其实就是锁, 但是为了符合RAII标准,用lock_guard把std::mutex转换成符合RAII标准的锁

lock_guard在构造时自动上锁

析构时自动解锁, 所以在退出作用域时自动解锁

换句话说, std::lock_guard就是在声明的时候上锁, 退出作用域自动解锁, 完全可以理解为std::mutex

#include<mutex>

std::mutex mtx;

// func1 == func2 !

void func1() {
	std::lock_guard lock(mtx);
    // do_sth
}

void func2() {
    mtx.lock();
    // do_sth
    mtx.unlock();
}
// 自己限定作用域
#include<mutex>

std::mutex mtx;

void func() {
    // area
    {
        // 锁的作用范围
        std::lock_guard lock(mtx);
    }
    // 不锁了
}

3. 符合RAII标准并且更自由:unique_lock

lock_guard只能在退出作用域的时候解锁, 不能自己解锁, 就很沙比

unique_lock就可以理解为可以手动解锁的lock_guard

std::mutex mtx;

void func() {
    std::unique_lock ulock(mtx);
    // do_sth ...
    
    ulock.unlock();
    
    // 还可以重新上锁
    ulock.lock();
}
// 最后退出作用域不管之前怎么解锁上锁, 最后还是会检查一遍要不要解锁

4. 死锁

1. 死锁的预防: 破坏请求和保持条件:一次性上多个锁
1. std::lock()
std::mutex mtx1, mtx2;
void func() {
	std::lock(mtx1, mtx2);		// 同时上两个锁
    
    mtx1.unlock();
    mtx2.unlock();
    // 可以分别解锁
}
2. RAII版本: std::scoped_lock()

不解释, 还是退出作用域自动解锁

2. 单个线程也会死锁——使用递归锁避免: std::recursive_mutex

死锁举例

void other() {
    mtx.lock();
    // do_sth
    mtx.unlock();
}

std::mutex mtx;

void func() {
    mtx.lock();
    other();
    mtx.unlock();
    
}

在一个线程里锁两次,就死锁了, 把这里的锁替换成recursive_mutex, 这玩意里面自带计数器, 就不会死锁了

5. 读写锁: shared_mutex

特点: 读共享, 写互斥(操作系统的读写模型)
能允许的事情:
  • n个人读, 没人写
  • 一个人写, 没人读
  • 没人读没人写
接口:
std::shared_mutex smtx;

void func() {
	// 互斥
    smtx.lock();
    smtx.unlock();
    
    // 共享锁
    smtx.lock_shared();
    smtx.unlock_shared();
}

符合RAII:

std::shared_lock 根之前一样, 不多说

4. 硬件支持最大线程数

unsigned int max_thread_num = std::thread::hardware_concurrency();
lscpu	# 看看和输出是不是一样滴

5. cmake中引入pthread

set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
target_link_libraries(test PUBLIC Threads::Threads)

6. 同步: std::condition_variable

类似信号量, 根据条件阻塞和解锁

举例

#include<mutex>
#include<condition_variable>

std::mutex mtx;
std::condition_variable cv;

int i = 0;

void fun() {
    std::unique_lock ulock(mtx);
    while(i > 0) {
        // 当前线程进入等待
        cv.wait(ulock);
    }
}

void fun2() {
    // 唤醒陷入等待的线程
    cv.notify_one();
}

增加条件

cv.wait(lock, condition)

condition是一个lambda表达式, 当返回值为真才能唤醒

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

void fun() {
    std::unique_lock ulock(mtx);
    
    cv.wait(ulock, [&] { return ready; });
}

void fun2() {
    cv.notify_one();	// 不能唤醒fun()
    ready = true;
    cv.notify_one(); 	// fun被唤醒
   	cv.notify_all(); 	// 全部唤醒(如果有多个线程阻塞在cv)
}

notif

7. 线程池

并发中一般把任务作为一个类, 在任务中实现线程安全

把线程也实现一个类, 用容器装线程有关的信息

然后用线程池去创建线程, 任务对象方法线程的入口函数

#ifndef _THREADPOOL_H
#define _THREADPOOL_H
#include <vector>
#include <queue>
#include <thread>
#include <iostream>
#include <stdexcept>
#include <condition_variable>
#include <memory> //unique_ptr
#include<assert.h>

const int MAX_THREADS = 1000; //最大线程数目

template <typename T>
class threadPool
{
public:
    threadPool(int number = 1);//默认开一个线程
    ~threadPool();
    std::queue<T *> tasks_queue;		   //任务队列

    bool append(T *request);//往请求队列<task_queue>中添加任务<T *>

private:
    //工作线程需要运行的函数,不断的从任务队列中取出并执行
    static void *worker(void *arg);
    void run();

private:
    std::vector<std::thread> work_threads; //工作线程

    std::mutex queue_mutex;
    std::condition_variable condition;  //必须与unique_lock配合使用
    bool stop;

};//end class

//构造函数,创建线程
template <typename T>
threadPool<T>::threadPool(int number) : stop(false)
{
    if (number <= 0 || number > MAX_THREADS)
        throw std::exception();
    for (int i = 0; i < number; i++)
    {
        std::cout << "created Thread num is : " << i <<std::endl;
        work_threads.emplace_back(worker, this);//添加线程
        //直接在容器尾部创建这个元素,省去了拷贝或移动元素的过程。
    }
}
template <typename T>
inline threadPool<T>::~threadPool()
{

    std::unique_lock<std::mutex> lock(queue_mutex);
    stop = true;
    
    condition.notify_all();
    for (auto &ww : work_threads)
        ww.join();//可以在析构函数中join

}
//添加任务
template <typename T>
bool threadPool<T>::append(T *request)
{
    /*操作工作队列时一定要加锁,因为他被所有线程共享*/
    queue_mutex.lock();//同一个类的锁
    tasks_queue.push(request);
    queue_mutex.unlock();
    condition.notify_one(); //线程池添加进去了任务,自然要通知等待的线程
    return true;
}
//单个线程
template <typename T>
void *threadPool<T>::worker(void *arg)
{
    threadPool *pool = (threadPool *)arg;
    pool->run();//线程运行
    return pool;
}
template <typename T>
void threadPool<T>::run()
{
    while (!stop)
    {
        std::unique_lock<std::mutex> lk(this->queue_mutex);
        /* unique_lock() 出作用域会自动解锁 */
        this->condition.wait(lk, [this] { return !this->tasks_queue.empty(); });
        //如果任务为空,则wait,就停下来等待唤醒
        //需要有任务,才启动该线程,不然就休眠
        if (this->tasks_queue.empty())//任务为空,双重保障
        {
            assert(0&&"断了");//实际上不会运行到这一步,因为任务为空,wait就休眠了。
            continue;
        }
        else
        {
            T *request = tasks_queue.front();
            tasks_queue.pop();
            if (request)//来任务了,开始执行
                request->process();
        }
    }
}
#endif

8. atomic

为什么要使用atomic?

在cpu指令层面实现线程安全的变量, 就是在cpu指令层面实现的锁,比互斥锁开销更小,并发度更高

经典例子

#include<thread>
#include<atomic>
#include<iostream>
#include<vector>

int count = 0;

void add() {
    for(int i = 0; i < 1000; i++) {
        count++;
    }
}

int main() {
    std::vector<std::thread> thread_pool;
    for(int i = 0; i < 4; i++) {
        thread_pool.push_back(std::thread{[&] {
            add();
        }});
    }
    std::cout << cout << std::endl;			// 最后加出来结果小于等于4000 因为count++ 翻译成汇编不止一条指令,多个线程并发指令会乱(读后写,写后读)
    return 0;
}

// 解决方法
// 1. 上锁(std::mutex)
// 2. 使用atomic
std::atomic<int> count = 0; 	
std::atomic_int count = 0;
// 两种定义都可以

原子整数

不支持浮点数和自定义类型

#include<atomic>

std::atomic<int> count{100};

不可拷贝

底层原理

轻量级的锁, cpu指令提供支持,对原子变量的操作的指令不可分, 这样就保证了封闭性和可再现性

接口

返回load()

存入store()

9. 时间

#include<chrono>

// 时间段类型
std::chrono::seconds(30);		// 30s
std::chrono::minutes(30);		// 30min
std::chrono::milliseconds(30); 	// 30ms
std::chorono::microseconds(30)  // 30us

// 时间点类型
std::chrono::steady_clock::time_point tp// 精度较高的cpu时间(还有操作系统时间,这里不给出)

运算:

时间点 + 时间段 = 时间点

时间点 - 时间点 = 时间段


auto t0 = std::chrono::steady_clock::now();
auto t1 = t0 + std::chrono::seconds(30);
auto time_duration = t1 - t0;

与this_thread配合使用见上面

10. 异步

同步&异步

在并发中:

  • 同步: 完全按代码顺序执行
  • 异步: 由中断或者信号驱动, 不一定按照顺序执行

举例

#include<async>
#include<future>

void do_this() {
    ...
}


void do_other_things() {
    ...
}

int main() {
	std::future<int> res = std::async{[&] {
    	return do_this();
	}};		
    // future就是保证以后会有这个变量过来, 但现在没有
    // async是先挂起,用另一个线程偷偷在后台干这个函数,main就可以干自己的
    
    
    do_other_things();
    
    res.get();	// 这时候再获得挂起的函数的返回值, 如果此时在后台还没执行完, 就会等待函数执行完, 再得到函数的返回值
}


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++并发与异步知识点最全汇总 的相关文章

随机推荐

  • 解决Android Studio Flamingo创建项目时出现的问题

    问题1 使用Android Studio Flamingo创建项目时 IDE默认下载Gradle 8 0 但是下载速度特别慢 或者直接下载失败 解决办法 手动安装Gradle 第一步 使用浏览器下载gradle 8 0 bin zip 下载
  • pandas数据类型转换,astype与convert_dtypes

    共同点 都可实现数据类型的转换 不同点 1 astype 强制转换 将 pandas 对象强制转换为指定的 dtype 在转换为str时 空值也将被强制转换为str类型 2 convert dtypes 转换为最佳类型 将 pandas 对
  • MinGw 和 cygwin 的区别和联系

    原创 by zoe zhang 1 windows与Linux操作系统的不同 windows和Linux是目前来说最流行的两大操作系统 在基本设计概念上 进程 线程 寻址 内存管理等方面都是大同小异的 但是二者之间的程序不兼容 因为二者在系
  • 零样本目标检测--GTNet: Generative Transfer Network for Zero-Shot Object Detection

    一些必要知识 1 零样本学习 Zero shot learning 一篇是 Lampert 2009年在CVPR上发表的Learning to Detect Unseen Object Class by Between Class Attr
  • AcWing 1055. 股票买卖 II

    输入样例1 6 7 1 5 3 6 4 输出样例1 7 输入样例2 5 1 2 3 4 5 输出样例2 4 输入样例3 5 7 6 4 3 1 输出样例3 0 样例解释 样例1 在第 2 天 股票价格 1 的时候买入 在第 3 天 股票价格
  • 自动驾驶汽车传感器融合系统及多传感器数据融合算法浅析

    本文转自电子技术设计 知乎小鹏汽车 作者 Hannes Estl 德州仪器 TI 汽车ADAS部门的总经理 如需转载请注明来源 原文没找到 只找到知乎小鹏汽车的一个回答https zhuanlan zhihu com p 23881606
  • JAVA开发(神乎其神的区块链技术之数据上链)

    这是我第二遍写关于区块链的博文 前一篇文章 神乎其神的区块链概念和技术 主要介绍区块链的由来和基本概念 因为博主最近在做一个区块链项目 所以有时候也遇到一些概念性的知识需要去理解 比如数据的上链 谈到数据上链 我们先了解一下现在都有什么链
  • Combine中类似Rxswift中的onNext

    其实Comibne中也有类似的方法 就是它handleEvents cancellable integers publisher handleEvents receiveSubscription subs in print Subscrip
  • tensorflow-1.14 版本更新

    使用TensorFlow训练文本筛选 错误提示 AttributeError module tensorflow python platform flags has no attribute mark flag as required 由于
  • VMware安装GHOST版XP教程

    VMware安装GHOST版XP教程 本来我是无法安装GHOST版的XP系统在VMware上 我很苦恼 到处找方法 最后找到了这里 可是每个关于这个问题的帖子里边都说改问题早就被处理 让搜索老帖子 可是我搜索出来的帖子里边的回复都说是改问题
  • 近期数据挖掘学习_计划安排及相关资料(定期更新)

    理论学习 学习主线 1 机器学习 统计学习方法 李航 机器学习 周志华 机器学习笔记 吴恩达 Scikit Learn文档 2 统计学复习 深入浅出统计学 statistics for business economics by ander
  • 零基础入门STM32编程——点灯(HAL库)(六)

    系列教程 定时器原理与配置 系列教程 GPIO原理与配置原则 前情回顾 通过前面几篇的学习 见目录 我们对STM32的基本架构以及原理有了一定了解 对GPIO的概念了有一定的认识 接下来通过一个简单的点灯项目 进步学习STM32编程 一 项
  • 使用linux主义的问题

    第一点 看看是否有服务 没有则apt get install 第二点 更改文件后更新文件 source 文件 第三点 权限 一定要看看权限 否则上传或其他操作则不被允许
  • Python基础知识(第二天)

    链式赋值 系列解包覆值 常量 链式赋值 x y 123 相当于 x 123 y 123 系列解包覆值 a b c 4 5 6 相当于 a 4 b 5 c 6 常量 Python 不支持常量 即没有语法规则限制改变一个常量的值 我们只能约定常
  • 雅虎、领英接连退出中国,开发者:GitHub 也会受到影响吗?

    继半个月前微软宣布关闭领英 即 LinkedIn 在华业务后 本周二 雅虎也宣布了最新消息 自 2021 年 11 月 1 日起 用户将无法从中国大陆使用 Yahoo 的产品与服务 一时之间 许多人将这两起事件结合在一起 也由此引发了开发人
  • Windows使用C++模拟鼠标点击----防止校园网掉线--登录校园网

    Linux模拟鼠标使用shell脚本就可以实现了 可以搜一下就可以解决 Windows模拟鼠标点击使用Python总会出现问题 所以使用C 来实现 1 使用gl c include
  • 电脑商城项目总结-01用户管理模块(注册,登录,修改密码,个人信息,上传头像)

    目录 部分图片展示 application properties 创建数据库并且验证是否静态资源能够正常访问 创建用户表 实体类 持久层 业务层 控制层 拦截器 单元测试 部分图片展示 以下是大体上的代码 application prope
  • Mac平台VMware Fusion虚拟机无网络连接与解决方法

    打开设置Network 点击下方锁子打开权限后点击 新增一个 把所有能打的对勾都打上 打开虚拟机后点击上面的 lt gt 然后把对勾打到新增的那个网络设置上 然后重启 不是挂起 而是重启
  • SpringBoot修改端口号不生效

    springboot中端口失效问题 idea中除了在配置文件中配置端口 还可在Edit Configurations中配置端口号 以往在这里配置端口号都可生效 此次失效是因为 当前模块依赖的模块中resource文件未指定为资源文件 上图中
  • C++并发与异步知识点最全汇总

    c 并发 文章目录 c 并发 1 thread 2 this thread命名空间 3 互斥 1 mutex 2 符合RAII标准的锁 lock guard 3 符合RAII标准并且更自由 unique lock 4 死锁 1 死锁的预防