C++标准学习--多线程

news2024/9/22 5:41:26

在以往多线程的实现的时候,都是自己去亲自创建线程,采用特殊flag 及锁控制线程的运转状态。这无可厚非,但又似乎有重复造轮子的嫌疑。最近发现了一个线程池的轮子,很不错,ZZ一下。

C++多线程+线程池(全详解) - 知乎 (zhihu.com)

多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。在一个程序中,这些独立运行的程序片段叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理”。

线程池

 安全工作队列实现

#include<queue>
#include<mutex>
template<typename T>
class safequeue
{
  private:
  std::queue<T>m_queue;
  std::mutex m_mutex;

  public:
  safequeue();
  safequeue(safequeue &&other);
  ~safequeue();

  bool empty()
  {
     std::unique_lock<std::mutex>lock(m_mutex);
     return m_queue.empty();
  }
  int  size()
  {
     std::unique_lock<std::mutex>lock(m_mutex);
     return m_queue.size();
  }
  void enqueue(T &t)
  {
     std::unique_lock<std::mutex>lock(m_mutex);
     m_queue.emplace(&t);
  }
  bool dequeue(T &t)
  {
    std::unique_lock<std::mutex>lock(m_mutex);
    if(m_queue.empty())
    return false;
    t=std::move(m_queue.front());
    m_queue.pop();
    return true;
  }
};

 线程池

#include<functional>
#include<mutex>
#include<vector>
#include<D:\code\c++project\C++\src\project\safequeue.c++>
#include<thread>
#include<condition_variable>
#include<future>
class threadpool
{
    private:
    class threadworker
    {
       private:
       int m_id;
       threadpool *m_pool;

       public:
       threadworker(const int id,threadpool *pool):m_id(id),m_pool(pool){}

       void operator()()//重载操作
       {
            std::function<void()> func;
            bool dequeued;
            if(!m_pool->m_shutdown)
            {
                std::unique_lock<std::mutex>lock(m_pool->m_conditional_mutex);
                if(m_pool->m_queue.empty())
                {
                    m_pool->m_conditional_lock.wait(lock);
                }
                dequeued=m_pool->m_queue.dequeue(func);
            }
            if(dequeued)
            {
                func();
            }
       }
    }; 
       bool m_shutdown;
       std::vector<std::thread> m_threads;
       safequeue<std::function<void()>> m_queue;
       std::mutex m_conditional_mutex;
       std::condition_variable m_conditional_lock;
    
    public:
    threadpool(const int n_threads=4):m_threads(std::vector<std::thread>(n_threads)),m_shutdown(false){}

    threadpool(const threadpool &)=delete;
    threadpool(threadpool &&)=delete;
    threadpool &operator=(const threadpool &)=delete;
    threadpool &&operator=(threadpool &&)=delete;

    void init()//初始化分配线程
    {
        for(int i=0;i<m_threads.size();i++)
        {
            m_threads.at(i)=std::thread(threadworker(i,this));
        }
    }

    void shutdown()//关闭线程
    {
        m_shutdown=true;
        m_conditional_lock.notify_all();
        for(int i=0;i<m_threads.size();i++)
        {
              if( m_threads.at(i).joinable())
              {
                  m_threads.at(i).join();
              }
        }
    }
    
    template<typename F,typename... Args>
    auto submit(F &&f,Args &&...args)->std::future<decltype(f(args...))>
    {
//这段C++代码定义了一个名为`submit`的函数模板。它接受一个可调用对象`f`和一系列参数`args`。

//函数模板使用了右值引用和可变参数模板的特性。`F &&f`表示对可调用对象`f`进行右值引用,`Args &&...args`表示可变数量的参数`args`,它们都是右值引用。

//返回类型使用了`std::future<decltype(f(args...))>`,表示返回一个`std::future`对象,该对象的类型是通过调用`f(args...)`来推断的。

//函数体中没有具体的实现,因此代码块中的大括号是空的。这意味着在使用这个函数模板时,需要根据具体的需求来提供实现。

//这段代码的目的是定义一个通用的函数模板,用于提交任务并返回一个`std::future`对象,以便在将来某个时刻获取任务的结果。通过使用右值引用和可变参数模板,可以接受不同类型的可调用对象和参数,并返回相应的`std::future`对象。

//总而言之,这段代码定义了一个通用的函数模板`submit`,用于提交任务并返回一个`std::future`对象,以便在将来获取任务的结果。具体的实现需要根据具体的需求来提供。

         std::function<decltype(f(args...))()> func=std::bind(std::forward<F>(f),std::forward<Args>(args)...);//forward为完美转发


         auto task_ptr=std::make_shared<std::packaged_task<decltype(f(args...))()>>(func);
         
         std::function<void()>task=[task_ptr]()
         {
               (*task_ptr)();
         };
//这段C++代码创建了一个`std::function`对象`wrapper_func`,并使用Lambda表达式作为其可调用对象。

//Lambda表达式`[task_ptr]() { (*task_ptr)(); }`定义了一个匿名函数,没有参数,返回类型为`void`。在函数体中,通过解引用`task_ptr`,调用了指针所指向的可调用对象。

//这个Lambda表达式被用作初始化`std::function<void()>`对象`wrapper_func`,因此`wrapper_func`成为了一个可调用对象,可以像函数一样被调用。

//这段代码的作用是将一个指针`task_ptr`所指向的可调用对象进行封装,并通过`std::function`对象`wrapper_func`来调用该可调用对象。通过这种方式,可以将一个具体的任务对象包装成一个可调用对象,并进行进一步的处理和调用。

//总而言之,这段代码创建了一个`std::function`对象,并使用Lambda表达式将一个指针所指向的可调用对象进行封装。这样可以通过`std::function`对象来调用该可调用对象,并进行进一步的处理。
         m_queue.enqueue(task);

         m_conditional_lock.notify_one();

         return task_ptr->get_future();
    }
};

线程池测试

#include<iostream>
#include<D:\code\c++project\C++\src\project\threadpool.c++>
#include <random>

std::random_device rd; // 真实随机数产生器

std::mt19937 mt(rd()); //生成计算随机数mt

std::uniform_int_distribution<int> dist(-1000, 1000); //生成-1000到1000之间的离散均匀分布数

auto rnd = std::bind(dist, mt);

// 设置线程睡眠时间
void simulate_hard_computation()
{
    std::this_thread::sleep_for(std::chrono::milliseconds(2000 + rnd()));
}

// 添加两个数字的简单函数并打印结果
void multiply(const int a, const int b)
{
    simulate_hard_computation();
    const int res = a * b;
    std::cout << a << " * " << b << " = " << res << std::endl;
}

// 添加并输出结果
void multiply_output(int &out, const int a, const int b)
{
    simulate_hard_computation();
    out = a * b;
    std::cout << a << " * " << b << " = " << out << std::endl;
}

// 结果返回
int multiply_return(const int a, const int b)
{
    simulate_hard_computation();
    const int res = a * b;
    std::cout << a << " * " << b << " = " << res << std::endl;
    return res;
}

void example()
{
    // 创建3个线程的线程池
    threadpool pool(3);

    // 初始化线程池
    pool.init();

    // 提交乘法操作,总共30个
    for (int i = 1; i <= 3; ++i)
        for (int j = 1; j <= 10; ++j)
        {
            pool.submit(multiply, i, j);
        }

    // 使用ref传递的输出参数提交函数
    int output_ref;
    auto future1 = pool.submit(multiply_output, std::ref(output_ref), 5, 6);

    // 等待乘法输出完成
    future1.get();
    std::cout << "Last operation result is equals to " << output_ref << std::endl;

    // 使用return参数提交函数
    auto future2 = pool.submit(multiply_return, 5, 3);

    // 等待乘法输出完成
    int res = future2.get();
    std::cout << "Last operation result is equals to " << res << std::endl;

    // 关闭线程池
    pool.shutdown();
}

int main()
{
    example();

    return 0;
}

 该轮子通过

std::thread(threadworker(i,this))

来创建线程,work后的()遭到重载,进行等待信号及dequeue动作,来调用queue内进入的func。而submit的作用是将func们压入queue。并提示线程们有新压入进来的func。 

线程池内有处理函数,有处理结果查询函数,二者怎么协调动作以达到最佳整体运转性能是需要考虑的地方。这里的线程池轮子是将每个功能块的线程开辟统一到了一个模板下,每个功能块只需要注册自己的操作函数即可,具有一定的简洁性。不足之处是每个功能块需要共享模板的实现和声明。总之是具有一定的进步性的。

使用这个轮子的另一个难度地方是,如果func内需要调用功能块里的作用域的函数时候,是需要一些额外传值的。这在设计的时候是需要考虑到的。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机体系结构----缓存一致性/多处理机

本文严禁转载&#xff0c;仅供学习使用。参考资料来自中国科学院大学计算机体系结构课程PPT以及《Digital Design and Computer Architecture》、《超标量处理器设计》、同济大学张晨曦教授资料。如有侵权&#xff0c;联系本人修改。 本文衔接上文计算机体系结构----存储系统 …

Leetcode18-算术三元组的数目(2367)

1、题目 给你一个下标从 0 开始、严格递增 的整数数组 nums 和一个正整数 diff 。如果满足下述全部条件&#xff0c;则三元组 (i, j, k) 就是一个 算术三元组 &#xff1a; i < j < k &#xff0c; nums[j] - nums[i] diff 且 nums[k] - nums[j] diff 返回不同 算术三…

【 ATU 随笔记 - Inverter 】PV Inverter 太阳能逆变器市场分析

一、简介 在上一篇的介绍中与大家分享了Micro Inverter ( 微型逆变器 )的用途与特色&#xff0c;也提到 Micro Inverter 适合家庭或是一些小型企业的需求。太阳能作为再生能源的代表&#xff0c;在当今能源转型中扮演着重要角色&#xff0c;也是有大型企业、大型能源站的需求&a…

【JavaScript】深度理解js的函数(function、Function)

简言 学了这么久的JavaScript&#xff0c;函数在JavaScript中最常用之一&#xff0c;如果你不会函数&#xff0c;你就不会JavaScript。 函数就是Function对象&#xff0c;一个函数是可以通过外部代码调用的一个“子程序”&#xff0c;它是头等&#xff08;first-class&#xf…

基于springboot+vue2的灾区物资管理系统(Java毕业设计)

大家好&#xff0c;我是DeBug&#xff0c;很高兴你能来阅读&#xff01;作为一名热爱编程的程序员&#xff0c;我希望通过这些教学笔记与大家分享我的编程经验和知识。在这里&#xff0c;我将会结合实际项目经验&#xff0c;分享编程技巧、最佳实践以及解决问题的方法。无论你是…

Unity图片导入趣事随笔

像这样的png格式的图片&#xff0c;直接导入unity时unity会把没有像素的部分用黑色填充&#xff0c;并根据填充部分自动生成alpha通道。看起来alpha通道是不能手动覆盖的&#xff0c;即使在ps中手动添加一个alpha通道&#xff0c;并添加覆盖值。 导出后也会发现这没有任何意义&…

整合junit与热部署

整合junit <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><version>2.7.0</version></dependency> 测试类上添加SpringBootTest 如&#xff1a; 注意测试类的…

第11章 GUI Page496~498 步骤三十二:打开画板文件01

tool_4_save_load.hpp添加新内容&#xff1a; 源文件中&#xff0c;新增的四个函数实现为&#xff1a; 为各图元类加上从流中加载图元数据的功能&#xff0c;先是接口声明&#xff1a; 各图元实现接口&#xff1a; 直线&#xff1a; 圆&#xff1a; 十字形&#xff1a; 方框&a…

【PostgreSQL创建索引的锁分析和使用注意】

1.1 创建普通B-tree索引的整体流程 如下是梳理的创建普通B-tree索引的大概流程&#xff0c;可供参考。 1.校验新索引的Catalog元数据|语法解析 ---将创建索引的sql解析成IndexStmt结构&#xff5c;校验B-Tree的handler -----校验内核是否支持该类型的索引,在pg_am中查找&q…

C++STL

STL基本概念 standard template library : 标准模板库STL从广义上可以分为&#xff1a; 容器(container) 算法(algorithm) 迭代器(iterator)。 容器和算法之间通过迭代器进行无缝连接。 STL几乎所有的代码都采用了模板类或者模板函数STL六大组件 STL的容器 STL的容器就是将运…

JAVA实现循环日期加一天

一、业务背景 现在数据库新增字段需要区分平日(0)和假期(1)的数据&#xff0c;之前有一批去年的数据都没有算过&#xff0c;所以得用日期循环来根据实际的时间来修改对应的数值&#xff0c;废话不多说看具体操作方法。 二、操作方法 // 初始日期 String dateString "20…

解密Mybatis-Plus:优雅简化你的数据访问层!

目录 1、引言 2、什么是Mybatis-Plus 3、Mybatis-Plus的特点和优势 4、安装和配置Mybatis-Plus 5、使用Mybatis-Plus进行数据库操作 6、Mybatis-Plus的高级功能 7、Mybatis-Plus的扩展和插件 8、与Spring Boot集成 9、结语 1、引言 Mybatis-Plus是一个强大而优雅的Jav…

idea中使用Lombok 失效,@Slf4j 找不到符号的解决办法

文章目录 一、前言二、问题排查和解决方案三、 其他解决方案3.1 另一种解决方案3.2 参考文章 一、前言 今天在一个多module工程中&#xff0c;新增了一个 springboot&#xff08;版本 2.2.4.RELEASE&#xff09; module&#xff0c;像往常一样&#xff0c;我引入了lombok依赖&…

2D绘图--视口窗口setViewport setWindow

目录 1 setViewport setWindow 2 示例 3 实际应用&#xff08;个人理解&#xff09; 4 总结 1 setViewport setWindow 在Qt中&#xff0c;QPainter的setViewport()方法用于定义绘图区域在窗口坐标系中的可视部分。 QPainter::setWindow() 是 Qt 库中 QPainter 类的一个方法…

【从零开始学习Java重要集合】深入解读ThreadLocal类

目录 前言&#xff1a; ThreadLocal&#xff1a; ThreadLocal的内部结构&#xff1a; ThreadLocal的常用方法&#xff1a; 1.set方法&#xff1a; 2.get方法&#xff1a; 3.setInitialValue方法 remove方法&#xff08;&#xff09;&#xff1a; ThreadLocalMap&…

Kubernetes 集群管理—日志架构

日志架构 应用日志可以让你了解应用内部的运行状况。日志对调试问题和监控集群活动非常有用。 大部分现代化应用都有某种日志记录机制。同样地&#xff0c;容器引擎也被设计成支持日志记录。 针对容器化应用&#xff0c;最简单且最广泛采用的日志记录方式就是写入标准输出和标…

stm32 - 基础架构

stm32 - 基础架构 基础架构外设概念系统结构引脚定义晶振工程 基础架构 外设概念 NVIC &#xff08;内核外设&#xff09; SysTick &#xff08;内核外设&#xff09; 其他是片上外设 系统结构 内核引出三条总线 ICode 指令总线&#xff1a; 连接Flash闪存&#xff08;编写的…

数据结构与算法之美学习笔记:47 | 向量空间:如何实现一个简单的音乐推荐系统?

这里写自定义目录标题 前言算法解析总结引申 前言 本节课程思维导图&#xff1a; 很多人都喜爱听歌&#xff0c;以前我们用 MP3 听歌&#xff0c;现在直接通过音乐 App 在线就能听歌。而且&#xff0c;各种音乐 App 的功能越来越强大&#xff0c;不仅可以自己选歌听&#xff0…

【Docker】Linux中Docker镜像结构及自定义镜像,并且上传仓库可提供使用

目录 一、镜像结构 1. 基本结构 2. 常用命令 二、自定义镜像 1. 基本镜像 2. 进阶镜像 3. 完善镜像 三、镜像上传仓库 每篇一获 一、镜像结构 自定义 Docker 镜像有很多用途&#xff0c;以下是一些主要的应用场景&#xff1a; 一致性环境&#xff1a;通过自定义镜像&a…

PPT文档怎么转换PDF?一个方法教你快速实现

在我们的办公、学习中难免会遇到需要将ppt转pdf文件的需求。现在的网络中有各种各样的PDF转换工具&#xff0c;有些操作很复杂&#xff0c;有些需要下载软件非常麻烦。接下来&#xff0c;给大家分享一款草最简单还不用下载软件的PPT转PDF&#xff08;https://www.yasuotu.com/p…