C++多线程学习笔记

创建线程(thread)

#include<iostream>
#include<thread>
using namespace std;

// 函数fun,接收一个整型参数并在无限循环中打印递增的值
void fun(int a) {
    while(1) {
        cout << ++a << "\n"; // 打印自增后的a
        this_thread::sleep_for(chrono::microseconds(50)); // 线程休眠50微秒
    }
}

int main() {
    int a = 0; // 初始化变量a为0
    thread t1(fun, a); // 创建线程t1,并启动,执行fun函数,传入a作为参数
    cout << t1.get_id() << endl; // 获取并打印线程t1的ID
    // t1.detach(); // 线程与主线程分离,独立运行
    t1.join(); // 等待线程t1执行完毕后再继续执行主线程的后续代码
    return 0; // 返回0,程序结束
}

互斥量(mutex),原子变量(atomic)

使用mutex互斥量

#include<iostream>
#include<thread>
#include<mutex>
using namespace std;

mutex mtx;  // 定义互斥量

int gg = 0;  // 全局变量 gg,作为共享资源

void fun() {
    int t = 1000;
    while (t--) {
        mtx.lock();  // 上锁
        ++gg;  // 修改共享资源
        --gg;  // 修改共享资源
        mtx.unlock();  // 解锁
    }
}

int main() {
    thread t1(fun);  // 创建线程 t1
    thread t2(fun);  // 创建线程 t2

    t1.join();  // 等待线程 t1 结束
    t2.join();  // 等待线程 t2 结束

    cout << gg;  // 输出共享资源 gg 的值
    return 0;
}

多个锁的情况

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

mutex mtx1, mtx2; // 定义两个互斥量

int gg = 0;

void fun() {
    int t = 1000;
    while (t--) {
        // 同时对两个互斥量上锁
        lock(mtx1, mtx2);
        
        ++gg; // gg自增
        --gg; // gg自减
        
        mtx1.unlock(); // 解锁第一个互斥量
        mtx2.unlock(); // 解锁第二个互斥量
    }
}

int main() {
    thread t1(fun); // 创建线程t1
    thread t2(fun); // 创建线程t2
    
    t1.join(); // 等待线程t1结束
    t2.join(); // 等待线程t2结束
    
    cout << gg; // 输出gg的值
    return 0;
}

实际开发中不会直接使用互斥量,而是搭配模板lock_guard使用,或者搭配功能更多的模板unique_lock使用

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

using namespace std;

int gg = 0;
mutex mtx1;
mutex mtx2;

void fun() {
    int t = 1000;
    while (t--) {
        lock_guard<mutex> lock1(mtx1);  // 使用lock_guard自动管理锁,作用域结束自动解锁
        lock_guard<mutex> lock2(mtx2);  // 同时锁定两个mutex,确保线程安全性
        ++gg;
        --gg;
    }
}

int main() {
    thread t1(fun);
    thread t2(fun);

    t1.join();
    t2.join();

    cout << gg;  // 输出最终的gg值,理论上应该为0,因为++gg和--gg成对出现

    return 0;
}

使用原子变量

#include <iostream>
#include <thread>
#include <atomic>
using namespace std;

atomic<int> gg = 0; // 原子变量 gg

void fun() {
    int t = 1000;
    while (t--) {
        ++gg;  // 原子操作:自增
        --gg;  // 原子操作:自减
    }
}

int main() {
    thread t1(fun);  // 创建线程 t1 执行 fun 函数
    thread t2(fun);  // 创建线程 t2 执行 fun 函数

    t1.join();  // 等待线程 t1 执行完毕
    t2.join();  // 等待线程 t2 执行完毕

    cout << gg;  // 输出原子变量 gg 的最终值
    return 0;
}

条件变量(condition_variable),信号量(semaphore)

#include <condition_variable>
#include <iostream>
#include <mutex>
#include <queue>
#include <thread>
using namespace std;

mutex mtx;              // 互斥量,用于保护共享资源
queue<int> q;           // 共享队列,生产者向其中推送数据,消费者从中取出数据
condition_variable cv;  // 条件变量,用于线程之间的同步

void producer() {
    int i = 0;
    while (true) {
        unique_lock<mutex> lock(mtx);   // 加锁,保护共享资源
        q.push(i);                      // 向队列中推送数据
        cout << "push: " << i << endl;  // 打印推送的数据
        cv.notify_one();                // 唤醒一个等待的消费者线程
        // cv.notify_all();             // 唤醒所有等待的消费者线程
        if (i < 9999)
            ++i;
        else
            i = 0;
    }
}

void consumer() {
    int data = 0;
    while (true) {
        unique_lock<mutex> lock(mtx);   // 加锁,保护共享资源
        while (q.empty())
            cv.wait(lock);              // 等待直到队列非空,解锁互斥量并等待条件变量通知后重新加锁
        data = q.front();               // 获取队列头部数据
        q.pop();                        // 弹出队列头部数据
        cout << "pop: " << data << '\n';// 打印弹出的数据
    }
}

int main() {
    thread t1(producer);    // 创建生产者线程
    thread t2(consumer);    // 创建消费者线程

    t1.join();              // 等待生产者线程结束
    t2.join();              // 等待消费者线程结束

    return 0;
}

信号量(semaphore)只在C++20之后的标准有(了解)

#include <iostream>
#include <thread>
#include <semaphore> // 包含信号量的头文件
using namespace std;

counting_semaphore<3> csem(0); // 定义一个初始计数为0的计数信号量,最多允许3个线程同时访问
binary_semaphore bsem(0); // 定义一个初始计数为0的二进制信号量,相当于一次只允许一个线程访问

void task() {
    cout << "线程开始等待信号量\n";
    csem.acquire(); // 线程等待并获取计数信号量
    cout << "线程获取到信号量,继续执行\n";
}

int main() {
    thread t0(task); // 创建线程 t0 执行 task 函数
    thread t1(task); // 创建线程 t1 执行 task 函数
    thread t2(task); // 创建线程 t2 执行 task 函数
    thread t3(task); // 创建线程 t3 执行 task 函数
    thread t4(task); // 创建线程 t4 执行 task 函数

    cout << "主线程释放信号量\n";
    csem.release(2); // 主线程释放2个信号量,唤醒等待的线程,因为初始设置是0,也就是要唤醒两个线程

    t0.join(); // 等待线程 t0 执行完毕
    t1.join(); // 等待线程 t1 执行完毕
    t2.join(); // 等待线程 t2 执行完毕
    t3.join(); // 等待线程 t3 执行完毕
    t4.join(); // 等待线程 t4 执行完毕

    return 0;
}

promise future

#include <future>
#include <iostream>
#include <thread>
using namespace std;

// 定义一个任务函数,接受一个整数参数和一个promise引用
void task(int a, promise<int> &r)
{
    // 将计算结果设置到promise中
    r.set_value(a + a);
}

int main()
{
    // 创建一个promise对象,用于在任务函数中设置值
    promise<int> p;
    // 从promise中获取future对象,用于获取任务函数的返回值
    future<int> f = p.get_future();
    
    // 创建一个线程,执行任务函数task,并传递参数1和promise对象p的引用
    thread t(task, 1, ref(p));
    
    /*
        在此处可以进行其他操作
    */
    
    // 输出future对象的值,注意:future的get方法只能调用一次
    cout << f.get();
    
    /*
        如果需要多次访问future对象的值,可以使用shared_future
        shared_future<int> s_f = f.share();
        这样可以直接值传递,而不是引用传递
    */
    
    // 等待线程执行完成
    t.join();
    
    return 0;
}

std::packaged_task std::async

async

#include <future>
#include <iostream>
using namespace std;

// 定义一个函数,计算两个整数的和
int task(int a, int b)
{
    return a + b;
}

int main()
{
    // 创建一个 future 对象,用 async 异步调用 task 函数,并传入参数 1 和 2
    future<int> fu = async(task, 1, 2);
    // 相当于 future<int> fu = async(launch::async|launch::deferred, task, 1, 2);
    // launch::async 会启动一个新线程执行任务
    // launch::deferred 会延迟调用任务,在需要获取结果时才调用
    // launch::async|launch::deferred 根据具体情况自动选择

    // 输出 future 对象的结果,使用 get() 函数获取异步任务的返回值
    cout << fu.get();

    return 0;
}

packaged_task

#include <future>
#include <iostream>
using namespace std;

// 定义一个任务,计算两个整数的和
int task(int a, int b)
{
    return a + b;
}

int main()
{
    // 创建一个打包任务,将函数 task 绑定到 packaged_task
    packaged_task<int(int, int)> t(task);
    
    // 执行任务,传入参数 1 和 2
    t(1, 2);
    
    // 获取任务的未来对象,并获取结果
    cout << t.get_future().get();
    
    return 0;
}

bind

#include <future>
#include <iostream>
using namespace std;

// 定义一个普通函数,返回两个整数的和
int task(int a, int b)
{
    return a + b;
}

int main()
{
    // 使用bind将函数task绑定到a,返回一个std::function对象
    auto a = bind(task, 1, 2); // 返回的是std::function

    // 调用a,计算绑定的函数结果
    int ret = a();
    cout << ret << endl;

    // 使用packaged_task封装a,packaged_task是一个可调用对象的包装器
    packaged_task<int()> t(a);
    t(); // 执行packaged_task,实际调用绑定的函数task

    // 获取packaged_task的future,等待任务完成并获取结果
    cout << t.get_future().get(); // 输出任务的结果

    return 0;
}

异步线程池

#include <vector>
#include <queue>
#include <memory>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <future>
#include <functional>
#include <stdexcept>
#include <iostream>
 
// 建议使用支持C++14以上的编译器以支持所有特性
class ThreadPool {
public:
    // 构造函数, 初始化大小
    ThreadPool(size_t);
 
    // typename std::result<F(Args...)> -> 编译期推断返回类型
    // 可以使用auto代替,自动推断返回类型
    template<class F, class... Args>
    auto enqueue(F &&f, Args &&... args)
    -> std::future<typename std::result_of<F(Args...)>::type>;
 
    // 析构函数
    ~ThreadPool();
 
private:
    // need to keep track of threads so we can join them
    // 线程池的工作线程数
    std::vector<std::thread> workers;
 
    // the task queue
    // 任务队列 函数应该被包装为void(void) 的task
    std::queue<std::function<void()> > tasks;
 
    // synchronization
    // 同步工具
    // 互斥锁和条件变量
    // stop变量检测是否关闭线程池,可以使用atomic<bool>代替
    std::mutex queue_mutex;
    std::condition_variable condition;
    bool stop;
};
 
// the constructor just launches some amount of workers
inline ThreadPool::ThreadPool(size_t threads)
        : stop(false) {
    // 创建threads个新线程塞进线程池
    // 使用std::move 右值传递
    for (size_t i = 0; i < threads; ++i)
        workers.emplace_back( // 相当于 push_back(std::move(...))
                [this] { // lambda函数,将class的成员变量以指针(引用)形式传递进去,*this则是以拷贝形式传递
                    for (;;) {
                        // worker函数不断轮询,竞争任务
                        // 创建一个任务包装,以存放将要完成的task
                        std::function<void()> task;
                        {
                            // 访问临界区需要上锁
                            std::unique_lock<std::mutex> lock(this->queue_mutex);
                            // 若队列不为空或者需要stop,则唤醒worker
                            this->condition.wait(lock,
                                                 [this] { return this->stop || !this->tasks.empty(); });
                            // 若有停止信号或者队列为空,则直接返回
                            if (this->stop && this->tasks.empty())
                                return;
                            // 获取任务,使用右值
                            task = std::move(this->tasks.front());
                            // 弹出在工作的任务
                            this->tasks.pop();
                        }
                        // 执行任务
                        task();
                        // 完成后继续从task队列中提取任务
                    }
                }
        );
}
 
// add new work item to the pool
template<class F, class... Args>
auto ThreadPool::enqueue(F &&f, Args &&... args)
// 下面的推断可以使用auto
-> std::future<typename std::result_of<F(Args...)>::type> {
    // 使用萃取的方法获取返回值类型
    using return_type = typename std::result_of<F(Args...)>::type;
 
    // 将任务包装成异步函数指针,封装为shared_ptr 完后后自动回收,不造成内存泄漏
    // 而且在后续的lambda函数中可以直接传递函数指针然后执行
    // 使用packaged_task<>,函数绑定std::bind,和完美转发std::forward
    // 包装需要执行的函数,然后在后台进行异步执行
    auto task = std::make_shared<std::packaged_task<return_type()> >(
            std::bind(std::forward<F>(f), std::forward<Args>(args)...));
 
    // 绑定异步函数task的返回值到future res中
    std::future<return_type> res = task->get_future();
 
    {
        // 在匿名作用域中使用unique_lock
        // 减小锁的粒度,出了匿名作用区锁就被释放
        std::unique_lock<std::mutex> lock(queue_mutex);
 
        // don't allow enqueueing after stopping the pool
        // 防止在停止后放入任务
        if (stop)
            throw std::runtime_error("enqueue on stopped ThreadPool");
 
        // 将匿名函数包装到lambda函数void()中
        // task是函数指针(即函数的地址),所以拷贝传递可以执行
        tasks.emplace([task]() { (*task)(); });
    }
    // 唤醒一个worker
    condition.notify_one();
    return res;
}
 
// the destructor joins all threads
inline ThreadPool::~ThreadPool() {
    {
        // 此处使用atomic<bool>显得更加方便
        std::unique_lock<std::mutex> lock(queue_mutex);
        stop = true;
    }
    condition.notify_all();
    // join后自动销毁回收
    for (std::thread &worker: workers)
        worker.join();
}

 
int main() {
 
    ThreadPool pool(4);
    std::vector<std::future<int> > results;
 
    results.reserve(8);
    for (int i = 0; i < 8; ++i) {
        results.emplace_back(
                pool.enqueue([i] {
                    std::cout << "hello " << i << std::endl;
                    std::this_thread::sleep_for(std::chrono::seconds(1));
                    std::cout << "world " << i << std::endl;
                    return i * i;
                })
        );
    }
 
    for (auto &&result: results)
        std::cout << result.get() << ' ';
    std::cout << std::endl;
 
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/781115.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

flask使用定时任务flask_apscheduler(APScheduler)

Flask-APScheduler描述: Flask-APScheduler 是一个 Flask 扩展&#xff0c;增加了对 APScheduler 的支持。 APScheduler 有三个内置的调度系统可供您使用&#xff1a; Cron 式调度&#xff08;可选开始/结束时间&#xff09; 基于间隔的执行&#xff08;以偶数间隔运行作业…

RabbitMq - Java客户端基础【简单案例 +Work模型】

目录 1、前置知识 1.1、AMQP怎么理解 1.2、Spring AMQP是什么 1.3、为什么要了解Spring-AMQP&#xff1f; 2、使用Spring-AMQP实现一个发消息案例 3、Work模型 问题&#xff1a; 优化&#xff1a; 小结&#xff1a;Work模型的使用&#xff1a; 1、前置知识 1.1、AMQP怎…

[激光原理与应用-101]:南京科耐激光-激光焊接-焊中检测-智能制程监测系统IPM介绍 - 5 - 3C行业应用 - 电子布局类型

目录 前言&#xff1a; 一、激光在3C行业的应用概述 1.1 概述 1.2 激光焊接在3C-电子行业应用 二、3C电子行业中激光焊接 2.1 纽扣电池 2.2 均温板 2.3 指纹识别器 2.4 摄像头模组 2.5 IC芯片切割 三、3C行业中激光切割 四、激光在3C行业中的其他应用 4.1 涂层去除…

Towards Accurate and Robust Architectures via Neural Architecture Search

基于网络架构搜索的准确性与鲁棒性结构研究 论文链接&#xff1a;https://arxiv.org/abs/2405.05502 项目链接&#xff1a;未开源 Abstract 为了保护深度神经网络免受对抗性攻击&#xff0c;对抗性训练因其有效性而受到越来越多的关注。然而&#xff0c;对抗训练的准确性和鲁…

服务器本地部署文件服务器minio

minio类似于阿里云的OSS&#xff0c;为不方便把图、文、日志等形式的文件保存在公有云上的&#xff0c;可以在自己的服务器上部署文件服务器 看过本人前几个文章的&#xff0c;使用docker就会很快上手部署&#xff0c;直接上所有代码 #添加镜像 docker search minio docker p…

jvm 03 JVM的运行时数据区域 ,(类常量池,运行时常量池,字符串常量池这个三个的区别),操作系统内存模型JMM和JVM的内存模型联系

方法区在jdk8后&#xff0c;改成元空间 JVM内存模型&#xff1a; JMM 主内存&#xff1a;本地方法区和堆 工作内存&#xff1a;私有的工作栈 其实一个JVM内存模型&#xff08;主要就是运行时数据区域&#xff09;一个Java进程的JMM&#xff0c;工作内存JVM中线程的内存区域…

关于umjs的主题切换实现

注意本文写作日期2024年7月7日&#xff0c;我目前是最新版本的 注意&#xff1a;该功能仅 antd v5 可用 最后目标实现 先说一下&#xff0c;umijs布局默认是内置ant-design/pro-layout布局写的 看一下官网ProLayout - 高级布局和布局与菜单 直接在app.tsx加入以下&#xff…

Git管理源代码、git简介,工作区、暂存区和仓库区,git远程仓库github,创建远程仓库、配置SSH,克隆项目

学习目标 能够说出git的作用和管理源代码的特点能够如何创建git仓库并添加忽略文件能够使用add、commit、push、pull等命令实现源代码管理能够使用github远程仓库托管源代码能够说出代码冲突原因和解决办法能够说出 git 标签的作用能够使用使用git实现分支创建&#xff0c;合并…

磐维2.0数据库日常维护

磐维数据库简介 “中国移动磐维数据库”&#xff08;ChinaMobileDB&#xff09;&#xff0c;简称“磐维数据库”&#xff08;PanWeiDB&#xff09;。是中国移动信息技术中心首个基于中国本土开源数据库打造的面向ICT基础设施的自研数据库产品。 其产品内核能力基于华为 OpenG…

pyrender 离线渲染包安装教程

pyrender 离线渲染包安装教程 安装 安装 官方安装教程:https://pyrender.readthedocs.io/en/latest/install/index.html#installmesa 首先 pip install pyrenderclang6.0安装 下载地址:https://releases.llvm.org/download.html#6.0.0 注意下好是叫&#xff1a;clangllvm-6…

L04_MySQL知识图谱

这些知识点你都掌握了吗&#xff1f;大家可以对着问题看下自己掌握程度如何&#xff1f;对于没掌握的知识点&#xff0c;大家自行网上搜索&#xff0c;都会有对应答案&#xff0c;本文不做知识点详细说明&#xff0c;只做简要文字或图示引导。 1 基础 1.1内部组件结构 1.2 数据…

尚品汇-(十四)

&#xff08;1&#xff09;提交git 商品后台管理到此已经完成&#xff0c;我们可以把项目提交到公共的环境&#xff0c;原来使用svn&#xff0c;现在使用git 首先在本地创建ssh key&#xff1b; 命令&#xff1a;ssh-keygen -t rsa -C "your_emailyouremail.com" I…

用kimi实现一键实体识别与关系抽取

实体识别与关系抽取是自然语言处理&#xff08;NLP&#xff09;中的两个重要任务&#xff0c;通常被视为知识图谱构建的基础技术。 实体识别&#xff08;Named Entity Recognition, NER&#xff09;&#xff1a; 实体识别的目标是从文本中识别出具有特定意义的实体&#xff0…

动手学深度学习(Pytorch版)代码实践 -循环神经网络- 56门控循环单元(`GRU`)

56门控循环单元&#xff08;GRU&#xff09; 我们讨论了如何在循环神经网络中计算梯度&#xff0c; 以及矩阵连续乘积可以导致梯度消失或梯度爆炸的问题。 下面我们简单思考一下这种梯度异常在实践中的意义&#xff1a; 我们可能会遇到这样的情况&#xff1a;早期观测值对预测…

Nacos2.X源码分析:服务注册、服务发现流程

文章目录 Nacos2.1.X源码源码下载服务注册NacosClient端NacosServer端 服务发现NacosClient端NacosServer端 Nacos2.1.X源码 源码下载 源码下载地址 服务注册 官方文档&#xff0c;对于NamingService接口服务注册方法的说明 Nacos2.X 服务注册总流程图 NacosClient端 一个…

华为OSPF配置DR和BDR与指定DR

基础配置 <Huawei>sys #进入配置模式 Enter system view, return user view with CtrlZ. [Huawei]un in en #关闭报文弹窗 Info: Information center is disabled. [Huawei]sys R1 #设备名更改为R1 [R1]int g0/0/0 …

智能物联网鱼缸

硬件部分及接线图 工具 继电器、开发板、物联网os、云平台 微信小程序 结构&#xff1a;images、pages两个为主体。 标题头部分 <view class"container"> <view class"head_box"> <image src"/images/面性鱼缸.png"><…

【Java】详解String类中的各种方法

创建字符串 常见的创建字符串的三种方式&#xff1a; // 方式一 String str "hello world"; // 方式二 String str2 new String("hello world"); // 方式三 char[] array {a, b, c}; String str3 new String(array); "hello" 这样的字符串字…

昇思学习打卡-8-FCN图像语义分割

目录 FCN介绍FCN所用的技术训练数据的可视化模型训练模型推理FCN的优点和不足优点不足 FCN介绍 FCN主要用于图像分割领域&#xff0c;是一种端到端的分割方法&#xff0c;是深度学习应用在图像语义分割的开山之作。通过进行像素级的预测直接得出与原图大小相等的label map。因…

3-4 优化器和学习率

3-4 优化器和学习率 主目录点这里 优化器是机器学习和深度学习模型训练过程中用于调整模型参数的方法。它的主要目标是通过最小化损失函数来找到模型参数的最优值&#xff0c;从而提升模型的性能。 在深度学习中&#xff0c;优化器使用反向传播算法计算损失函数相对于模型参数…