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

在以往多线程的实现的时候,都是自己去亲自创建线程,采用特殊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内需要调用功能块里的作用域的函数时候,是需要一些额外传值的。这在设计的时候是需要考虑到的。

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

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

相关文章

理解Herbrand Equivalence

笔者最近在看GVN的一系列论文&#xff0c;总会看到一个概念叫Herbran Equivalence&#xff0c;依靠这种定义&#xff0c;能够判断一个GVN算法是否是complete的&#xff0c;也即检测一个算法是否是precise的&#xff0c;只有找到所有Herbrand Equivalence关系的算法才能称得上是…

Python基础知识:整理9 文件的相关操作

1 文件的打开 # open() 函数打开文件 # open(name, mode, encoding) """name: 文件名&#xff08;可以包含文件所在的具体路径&#xff09;mode: 文件打开模式encoding: 可选参数&#xff0c;表示读取文件的编码格式 """ 2 文件的读取 文…

18-链表-移除链表元素

这是链表的第18题&#xff0c;力扣链接。 给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.val val 的节点&#xff0c;并返回 新的头节点 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,6,3,4,5,6], val 6 输出&#xff1a;[1,2,3,…

杨中科 .NET项目结构及程序发布

一样的csproj,不一样的接口 1.文件包含于排除&#xff1a; 2. *.config 文件包含于排除 新建 .netcore 与 .netframework 项目 打开framework 项目文件位置 打开 frameworkConsoleApp1.csproj文件 查看 .netcore的CoreconsoleApp2.csproj文件 该文件十分简洁 改变版本…

springCould中的Bus-从小白开始【11】

目录 &#x1f9c2;1.Bus是什么❤️❤️❤️ &#x1f32d;2.什么是总线❤️❤️❤️ &#x1f953;3.rabbitmq❤️❤️❤️ &#x1f95e;4.新建模块3366❤️❤️❤️ &#x1f373;5.设计思想 ❤️❤️❤️ &#x1f37f;6.添加消息总线的支持❤️❤️❤️ &#x1f9…

java将word转换成pdf,并去除水印

注意我这里只是将word字节替换成pdf字节&#xff0c;如果是文件根据自己实际情况来做 1、所需jar包 <dependency><groupId>com.aspose</groupId><artifactId>aspose-words</artifactId><version>15.8.0</version></dependency&g…

模拟超市商品结算系统

要求:全程一个角色(管理员即用户) (1)需要管理员注册与登录 (2)管理员登录之后&#xff0c;可以进行上架新的商品(商品名称和单价) (3)管理员登录之后&#xff0c;也可以下架商品 (4)在节假日有优惠活动,可以对其中的一些商品修改相应的单价(价格提高和价格降低都可以) (5)用户…

JavaScript中alter、confrim、prompt的区别及使用

文章目录 一、alter1.什么是alert&#xff1f;2.alter的使用 二、confrim1.什么是confrim&#xff1f;2.confrim的使用 三、prompt1.什么是prompt&#xff1f;2.prompt的使用 四、总结alter、confrim、prompt的区别 一、alter 1.什么是alert&#xff1f; 在JavaScript中&…

在线问卷调查的优势:提升数据收集与分析效率的关键要素

无论是在工作中还是学习中&#xff0c;我们经常会使用问卷调查法来解决一些问题。不过&#xff0c;问卷调查有两种形式——线上和线下&#xff0c;这两者之间有什么优势和不足呢&#xff1f; 纸质问卷&#xff1a; 1、优势&#xff1a; 我们在使用纸质问卷的时候&#xff0c;通…

如何在Win10电脑接收苹果手机日程提醒呢?

有很多小伙伴手机使用的是iPhone苹果手机&#xff0c;但办公电脑使用的win10系统的电脑&#xff0c;这时候如果想要在win10电脑上同步接收苹果手机上设置的日程提醒&#xff0c;该怎么操作呢&#xff1f;如何在win10电脑接收苹果手机日程提醒呢&#xff1f; 如果你设置的日程提…

大数据-hive函数与mysql函数的辨析及练习-将多行聚合成一行

目录 1. &#x1f959;collect_list: 聚合-不去重 2. &#x1f959;collect_set(col): 聚合-去重 3. &#x1f959;mysql的聚合函数-group_concat 4. leetcode练习题 1. &#x1f959;collect_list: 聚合-不去重 将组内的元素收集成数组 不会去重 2. &#x1f959;collec…

C++指针小练习

双色球统计1-33个数字出现的次数(很详细) 做这个题一定要注意审题:题目要求是统计1-33个数字出现的次数,而不是前六个数字出现的次数 算法设计: ①:用一个数组p1来保存每一行的数据,再用一个数组p2来遍历1-33个数字,因为是要统计这33个数字出现的次数所以将数组初始化为0, ②…

二、Java中SpringBoot组件集成接入【MySQL和MybatisPlus】

二、Java中SpringBoot组件集成接入【MySQL和MybatisPlus】 1.MySQL和MybatisPlus简介2.maven依赖3.配置1.在application.yaml配置中加入mysql配置2.新增Mybatis-Plus配置类 4.参考文章 1.MySQL和MybatisPlus简介 MySQL是一种开源的关系型数据库管理系统&#xff0c;被广泛应用…

linux中出现不在 sudoers 文件中。此事将被报告的解决方法

出现如下提示gaokaoli 出现不在 sudoers 文件中。此事将被报告 一般是该用户 权限不够 既然知道权限不够可以添加到root用户组&#xff0c;获取权限即可 通过命令行添加到权限&#xff0c;发现还是不行 sudo usermod -g root gaokaoli 那就直接在配置文件中修改 通过执行vi…

中级Python面试问题

文章目录 专栏导读1、xrange 和 range 函数有什么区别&#xff1f;2、什么是字典理解&#xff1f;举个例子3、元组理解吗&#xff1f;如果是&#xff0c;怎么做&#xff0c;如果不是&#xff0c;为什么&#xff1f;4、 列表和元组的区别&#xff1f;5、浅拷贝和深拷贝有什么区别…

TS 36.331 V12.0.0-过程(2)-连接控制(1)-RRC连接建立

​本文的内容主要涉及TS 36.331&#xff0c;版本是C00&#xff0c;也就是V12.0.0。

TinyLog iOS v3.0接入文档

1.背景 为在线教育部提供高效、安全、易用的日志组件。 2.功能介绍 2.1 日志格式化 目前输出的日志格式如下&#xff1a; 日志级别/[YYYY-MM-DD HH:MM:SS MS] TinyLog-Tag: |线程| 代码文件名:行数|函数名|日志输出内容触发flush到文件的时机&#xff1a; 每15分钟定时触发…

TortoiseSVN·文件锁定与清理

安装 TortoiseSVN 的时候&#xff0c;选择 svn 命令可用, 选择 will be intalled on local hard drive 。 在锁定的文件夹内 cmd 进入终端&#xff0c;输入 find . -type f -name ".svn/lock" -exec rm -f {} \; 删除所有锁定文件。进行清理操作&#xff1a;svn clea…

一、数据结构基本概念

数据结构基本概念 一、数据结构基本概念1.基本概念和术语1.1数据&#xff08;Data&#xff09;1.2 数据元素&#xff08;Data element&#xff09;1.3 数据项 &#xff08;Data Item&#xff09;1.4 数据对象 &#xff08;Data Object&#xff09;1.5 数据结构 &#xff08;Dat…

JS栈和堆:数据是如何存储的

JS栈和堆&#xff1a;数据是如何存储的 背景JavaScript 是什么类型的语言JavaScript 的数据类型内存空间栈空间和堆空间再谈闭包 背景 JS有多种数据类型&#xff1a;数字型&#xff0c;字符串型&#xff0c;数组型等&#xff0c;虽然 JavaScript 并不需要直接去管理内存&#…