首页 > 编程语言 >C++多线程——线程池

C++多线程——线程池

时间:2023-10-30 16:35:53浏览次数:35  
标签:std function emplace back C++ vector 线程 多线程

线程池 Thread Pool


线程池简单来说就是用来管理多个线程的,以避免频繁开辟或销毁线程的情况。

以下是创建和使用线程池的基本步骤:

  • 创建一个任务队列,用于存储待执行的任务。

  • 创建一组线程,这些线程会从任务队列中获取任务并执行它们。

  • 将任务提交到任务队列中,由线程池的线程异步执行。

  • 线程池会不断地从任务队列中获取任务并执行,直到没有任务可执行。

C++标准库并没用提供内置的线程池实现,我们可以利用共享队列、std::thread 类型的vector数组、条件变量、互斥锁来完成。

以下是具体实现:

#include <iostream>
#include <vector>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>

class ThreadPool {
public:
    ThreadPool(size_t numThreads) {
        for (size_t i = 0; i < numThreads; ++i) {
            workers.emplace_back([this] {
                while (true) {
                    std::function<void()> task;
                    {
                        std::unique_lock<std::mutex> lock(queueMutex);
                        condition.wait(lock, [this] { return stop || !tasks.empty(); });
                        if (stop && tasks.empty()) {
                            return;
                        }
                        task = std::move(tasks.front());
                        tasks.pop();
                    }
                    task();
                }
            });
        }
    }

    void enqueueTask(std::function<void()> task) {
        {
            std::unique_lock<std::mutex> lock(queueMutex);
            tasks.emplace(std::move(task));
        }
        condition.notify_one();
    }

    ~ThreadPool() {
        {
            std::unique_lock<std::mutex> lock(queueMutex);
            stop = true;
        }
        condition.notify_all();
        for (std::thread &worker : workers) {
            worker.join();
        }
    }

private:
    std::vector<std::thread> workers;
    std::queue<std::function<void()>> tasks;
    std::mutex queueMutex;
    std::condition_variable condition;
    bool stop = false;
};

int main() {
    ThreadPool pool(4); // 创建一个具有4个线程的线程池

    // 提交任务给线程池
    for (int i = 0; i < 8; ++i) {
        pool.enqueueTask([i] {
            std::this_thread::sleep_for(std::chrono::seconds(1));
            std::cout << "Task " << i << " executed by thread " << std::this_thread::get_id() << std::endl;
        });
    }

    // 在任务执行完之前等待
    std::this_thread::sleep_for(std::chrono::seconds(5));

    return 0;
}


解释:
在线程池 ThreadPool 的构造函数中,我们传入了一个 numThrads 变量作为开辟线程的数量。实现起来也就是往 std::thread 的类型 vector 数组中添加 numThreads 个线程。在实现时,并不是先创建一个线程,再将线程通过 workers.push_back() 加入vector数组中,而是使用了效率更高的 workers.emplace_back() 方法。这个传入的参数不再是 std::thread 类型,而是 std::thread 类型构造函数参数。也就是 std::thread 执行的函数。std::thread 在这个地方执行的函数内容应该是发现tasks队列不空时,将task任务加入到线程中处理。具体处理是共享任务队列不空时,将任务传入线程池中处理(当队列为空时,不能进行取操作);当共享任务队列为空时,并且stoptrue时,线程结束。(这里的stop会在析构函数中变为 true,意味着当调用ThreadPool 的析构函数时,线程就应该都乖乖结束了)。enqueueTask(std::function<void()> task) 方法是将task 任务加入共享队列中,加入后利用条件变量通知(告诉线程池的构造函数中的workers数组,可以继续干活了)。在线程池销毁时,也就是调用线程池的析构函数时,会将 stop 设置为true,并且通知所有线程干活+join方法使得活干完

std::vector::emplace_back

std::vector::emplace_back 是 C++ 标准库中的一个函数,用于将元素添加到 std::vector 容器的末尾。与 push_back 不同,emplace_back 允许你在容器中构造元素,而不是先创建一个元素然后再将其复制到容器中。这可以提高性能,尤其是当元素类型是自定义类时。

emplace_back 的语法如下:

vector.emplace_back(args);

其中:

  • vector 是你的 std::vector 容器。
  • args 是构造元素的参数,可以是构造函数所需的参数。

一个使用 emplace_back 将自定义类的对象添加到 std::vector的示例:

演示 `emplace_back` 将自定义类的对象添加到 `std::vector`的示例:
#include <iostream>
#include <vector>

class MyClass {
public:
    MyClass(int a, double b) : value1(a), value2(b) {
        std::cout << "Constructor called" << std::endl;
    }

private:
    int value1;
    double value2;
};

int main() {
    std::vector<MyClass> myVector;

    // 使用 emplace_back 构造 MyClass 的对象并添加到 vector
    myVector.emplace_back(42, 3.14);

    // 使用 emplace_back 添加另一个对象
    myVector.emplace_back(100, 2.71);

    return 0;
}

在上面的示例中,emplace_back 允许你直接传递构造函数所需的参数,并在容器中构造对象。这避免了创建临时对象,从而提高了性能。注意,在构造时输出的 "Constructor called" 语句表明对象是在调用 emplace_back 时构造的。


std::function

std::function 是 C++ 标准库提供的一个通用函数封装类,它可以用来存储、传递和调用各种可调用对象(函数、函数指针、成员> 函数指针、lambda 表达式等)。std::function 提供了一种通用的方式来保存函数对象,使其可以在运行时动态确定调用的函数。

以下是 std::function 的主要特点和用途:

  1. 通用性std::function 可以存储各种可调用对象,包括函数、函数指针、成员函数指针、函数对象、lambda 表达式等。

  2. 类型擦除std::function 使用类型擦除的技术,因此可以在运行时存储和调用不同类型的可调用对象,而不需要在编译时> 确定具体类型。

  3. 函数指针的包装:它可以将函数指针包装成一个对象,以便在更复杂的函数签名情况下进行传递和调用。

  4. 函数回调std::function 可以用于实现回调机制,允许将函数或函数对象作为参数传递给其他函数或类,以便在特定事件> 发生时执行。

  5. 泛型编程:它常常用于泛型编程,使代码更具通用性,因为你可以以统一的方式处理不同的可调用对象。

一个简单的示例说明如何使用 `std::function`
#include <iostream>
#include <functional>

// 定义一个函数类型
using MyFunction = std::function<void(int)>;

void printNumber(int number) {
    std::cout << "Number: " << number << std::endl;
}

int main() {
    MyFunction func = printNumber; // 将函数指针包装成 std::function 对象
    func(42); // 调用包装的函数

    // 使用 lambda 表达式创建 std::function 对象
    MyFunction lambdaFunc = [](int x) {
        std::cout << "Lambda: " << x << std::endl;
    };
    lambdaFunc(123);

    return 0;
}

在这个示例中,我们首先将一个普通函数 printNumber 包装成 std::function 对象,并调用它。然后,我们使用 lambda 表达式创建另一个 std::function 对象,也调用它。这演示了 std::function 的通用性,可以容纳不同类型的可调用对象。

标签:std,function,emplace,back,C++,vector,线程,多线程
From: https://www.cnblogs.com/vLiion/p/17798159.html

相关文章

  • 在多线程里面,为什么推荐使用notifyAll而不是notify
    在多线程里面,为什么推荐使用notifyAll而不是notify?结论:notify容易造成死锁1、无论使用notify或者notifyAll,都是随机唤醒线程2、notify是随机唤醒一个线程去执行,noifyAll是唤醒所有线程,然后去抢占锁,去执行怎么产生死锁现象:P–生产者调用putproductC–消费者调用c......
  • Tornado实现多线程/多进程的HTTP服务
    用tornadoweb服务的基本流程原文链接1.实现处理请求的Handler,该类继承自tornado.web.RequestHandler,实现用于请求的对应方法如:get,post等。返回内容用self.write方法输出。**2.实例化一个Application。**构造函数的参数是一个Handler列表,通过正则表达式,将请求与Handler对应起来......
  • C++U4-02-贪心算法2
    上节课作业部分  [纪念品分组]  【算法分析】贪心算法:先对数组从小到大排序,用l=1,r=n指针指向首尾元素;如果pl+pr≤w,则将pl和pr分一组,指针l++,r--。如果pl+pr>w,则将pr单独作为一组,指针r--。如此反复直到取完所有元素。#include<iostream>#include<a......
  • C++U5-深度优先搜索-03(记忆化搜索、剪枝和优化)
    ......
  • C++23:多维视图(std::mdspan)
    C++23:多维视图(std::mdspan)介绍在C++23中,std::mdspan是一个非拥有的多维视图,用于表示连续对象序列。这个连续对象序列可以是一个简单的C数组、带有大小的指针、std::array、std::vector或std::string。这种多维视图通常被称为多维数组。多维数组的形状由其维数(也称为秩)和每个......
  • C++中低级内存操作
    C++中低级内存操作C++相较于C有一个巨大的优势,那就是你不需要过多地担心内存管理。如果你使用面向对象的编程方式,你只需要确保每个独立的类都能妥善地管理自己的内存。通过构造和析构,编译器会帮助你管理内存,告诉你什么时候需要进行内存操作。将内存管理隐藏在类中显著提高了可用性,......
  • 排序算法:选择排序,分别用c++、java、python实现
    选择排序介绍选择排序(SelectionSort)是一种简单的比较排序算法,它的工作原理如下:分区:将待排序的数组分成两个部分,一个部分是已排序的子数组,另一个部分是未排序的子数组。初始时,已排序的子数组为空,而未排序的子数组包含整个数组。选择最小值:从未排序的子数组中找到最小(或最大,根据......
  • 算法题:分别用c++/python/java实现回文数
    回文数是一个数字,从左到右和从右到左读都是相同的数字序列。换句话说,回文数在数值上是对称的。一些常见的回文数示例包括:单个数字:例如1、2、3等,它们本身就是回文数,因为它们只有一个数字。两位数:例如11、22、33等,它们也是回文数,因为它们的左右两个数字相同。多位数:例如121、1331、12......
  • 用c++写一个高精度计算的除法运算
    高精度除以低精度以下这段代码的主要作用是将一个大整数(以字符数组形式表示)除以一个整数,并输出结果。具体来说,代码将大整数a1(“1256”)除以整数b(3),并输出商。#include<iostream>#include<cstdio>#include<cstring>usingnamespacestd;intmain(){chara1[100]="1256";......
  • c++ .h头文件和.cpp源文件关系(转)
    https://www.cnblogs.com/fengzhengfly/p/8884581.htmlhttps://blog.csdn.net/qq_35452533/article/details/77282326头文件是声明,源文件是定义在cpp里包含.h,就会包含该h文件的cpp定义,所以需要在cpp里使用别的.h文件中的函数只能包含该.h,或者已包含的.h中已经包含的改.h,也就......