算法学习-线程池
目录
- 多线程与线程池
- 线程池的核心成员
- 线程池案例
- 1 运行环境以及目录
- 2 `ThreadPool.h`
- 3 `ThreadPool.cpp`
- 4 main.cpp
- 5、`CMakeLists.txt`
- 7、运行与打印
多线程与线程池
- 多线程:一种并发执行的技术,它允许在同一进程中同时执行多个线程。每个线程可以执行程序中的一部分代码,从而提高程序的执行效率。
- 线程池:一种设计模式,用于管理和复用线程。线程池维护着多个线程,等待监督管理者分配可并行执行的任务。这样避免了在短时间内创建和销毁线程的代价。
线程池的核心成员
1、任务队列,任务队列中存放需要线程执行的任务;
2、互斥锁,由于任务队列中是临界资源,被多个线程访问,需要互斥锁保证安全性;
3、条件变量,当任务队列不为空的时候或者需要停止线程池运行时唤醒线程;
4、工作线程,负责不断从任务队列中取出任务并执行。
5、线程池是否停止工作的标志;
运行逻辑如图所示:
线程池案例
1 运行环境以及目录
环境:Windows、Vscode、C++11
文件目录:
|—ThreadPool
|—main.cpp
|—ThreadPool.cpp
|—ThreadPool.h
|—CMakeLists.txt
2 ThreadPool.h
#define THREADPOOL_H
#include <vector>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional> // std::function 提供了一种更通用的函数绑定和调用机制
class ThreadPool{
public:
ThreadPool(size_t numWorkers, size_t maxTasks);
~ThreadPool();
void pushTask(std::function<void()> task); // 添加任务
void waitAll(); // 等待所有任务完成
private:
void workerLoop(); // 工作线程的循环
std::vector<std::thread> m_workers; // 工作线程
std::queue<std::function<void()>> m_tasks; // 任务队列
std::mutex m_mutex;
std::mutex m_print_mutex;
std::condition_variable m_cond;
std::condition_variable m_completionCond;
bool m_stop = false; // 线程池终止标志
size_t m_maxTasks; // 最大任务数
size_t m_activeTasks = 0; // 正在执行的任务数
};
#endif
3 ThreadPool.cpp
#include "ThreadPool.h"
#include <iostream>
// 线程池构造函数
ThreadPool::ThreadPool(size_t numWorkers, size_t maxTasks): m_maxTasks(maxTasks) {
for(size_t i = 0; i< numWorkers; ++i){
m_workers.emplace_back(&ThreadPool::workerLoop, this); // 创建工作线程并绑定到workerLoop函数上
// 构造函数启动线程,但线程不会立刻执行,启动存在延迟,需要等待线程执行完才会输出
// {
// std::lock_guard<std::mutex> lock(m_print_mutex); // 加锁,确保输出的顺序
// std::cout << "Thread " << i << " created." << "\tThread ID:"<< m_workers[i].get_id() << std::endl; // 输出线程创建信息
// }
}
}
// 线程池析构函数
ThreadPool::~ThreadPool(){
{
std::unique_lock<std::mutex> lock(m_mutex); // 加锁
m_stop = true; // 设置终止标志
}
m_cond.notify_all(); // 通知所有等待的线程
for(auto &worker : m_workers){ // 等待所有工作线程结束
if(worker.joinable()){
worker.join(); // 等待线程结束
}
}
}
// 向任务队列添加任务
void ThreadPool::pushTask(std::function<void()>task){
std::unique_lock<std::mutex> lock(m_mutex); // 加锁
m_cond.wait(lock, [this](){ return m_tasks.size() < m_maxTasks; }); // 等待任务队列有空位
m_tasks.emplace(std::move(task)); // 添加任务到队列
m_cond.notify_one(); // 通知一个等待的线程
}
// 线程循环函数
void ThreadPool::workerLoop(){
while(true){
std::function<void()> task; // 定义一个任务函数
{
std::unique_lock<std::mutex> lock(m_mutex); // 加锁
m_cond.wait(lock, [this]{ return m_stop || !m_tasks.empty(); }); // 等待任务队列有任务或者线程池终止
if(m_stop && m_tasks.empty()) return; // 如果线程池终止且任务队列为空,退出线程
task = std::move(m_tasks.front()); // 获取任务,.front()返回队列的第一个元素的引用
m_tasks.pop(); // 从队列中移除任务,.pop()移除队列的第一个元素
m_activeTasks++; // 增加正在执行的任务数
}
task(); // 执行任务
{
std::unique_lock<std::mutex> lock(m_mutex); // 加锁
m_activeTasks--; // 减少正在执行的任务数
if(m_activeTasks == 0){ // 如果所有任务都执行完毕
m_completionCond.notify_all(); // 通知所有等待的线程
}
// 当程序执行到该作用域的末尾时,lock对象会被销毁,自动释放互斥锁。
}
}
}
// 等待所有任务完成
void ThreadPool::waitAll(){
std::unique_lock<std::mutex> lock(m_mutex);
m_completionCond.wait(lock, [this]{ return m_activeTasks == 0 && m_tasks.empty(); }); // 等待所有任务完成
}
4 main.cpp
#include "ThreadPool.h"
#include <iostream>
#include <chrono>
#include <mutex>
std::mutex mtx; // 用于同步输出的互斥锁
void exampleTask(int taskId){
{
std::lock_guard<std::mutex> lock(mtx); // 加锁,确保输出的顺序
std::cout << "Task " << taskId << " started." << "\tThread ID is " << std::this_thread::get_id() << std::endl;
}
std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟任务执行时间
{
std::lock_guard<std::mutex> lock(mtx);
std::cout << "Task " << taskId << " finished." << "\tThread ID is " << std::this_thread::get_id() << std::endl;
}
}
int main(){
const size_t numThreads = 4; // 线程池中的线程数量
const size_t numTasks = 10; // 任务数量
ThreadPool pool(numThreads, numTasks); // 创建线程池
// 向线程池添加任务
for(int i = 0; i < numTasks; ++i){
pool.pushTask(std::bind(exampleTask, i)); // 使用std::bind绑定任务函数和参数
// pool.pushTask([i]{exampleTask(i);});
}
// 等待所有任务完成
pool.waitAll();
std::cout << "All tasks completed." << std::endl;
return 0;
}
5、CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(ThreadPoolV2)
# 设置C++标准
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 自动包含当前目录源文件
file(GLOB SOURCES CONFIGURE_DEPENDS
"*.cpp"
"*.h"
)
# 创建可执行目标
add_executable(threadpoolv2
${SOURCES}
)
# 多线程支持配置
if(CMAKE_CXX_COMPILE_ID MATCHES "GNU")
# 针对MinGW的特殊设置
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
target_link_libraries(threadpoolv2
PRIVATE
Threads::Threads
-static # 静态链接标准库
)
endif()
6、编译过程
mkdir build
cd build
cmake .. -G "MinGW Makefiles"
cmake --build .
如果运行成功,则大概出现以下打印内容
[ 33%] Building CXX object CMakeFiles/threadpool.dir/ThreadPool.cpp.obj
[ 66%] Building CXX object CMakeFiles/threadpool.dir/main.cpp.obj
[100%] Linking CXX executable threadpool.exe
[100%] Built target threadpool
7、运行与打印
./threadpool // 运行可执行程序
// 运行正确则打印如下内容:
Task 0 started. Thread ID is 5
Task 1 started. Thread ID is 3
Task 2 started. Thread ID is 2
Task 3 started. Thread ID is 4
Task 2 finished. Thread ID is 2
Task 4 started. Thread ID is 2
Task 1 finished. Thread ID is 3
Task 5 started. Thread ID is 3
Task 0 finished. Thread ID is 5
Task 3 finished. Thread ID is 4
Task 7 started. Thread ID is 4
Task 6 started. Thread ID is 5
Task 4 finished. Thread ID is 2
Task 8 started. Thread ID is 2
Task 7 finished. Thread ID is 4
Task 0 started. Thread ID is 5
Task 1 started. Thread ID is 3
Task 2 started. Thread ID is 2
Task 3 started. Thread ID is 4
Task 2 finished. Thread ID is 2
Task 4 started. Thread ID is 2
Task 1 finished. Thread ID is 3
Task 5 started. Thread ID is 3
Task 0 finished. Thread ID is 5
Task 3 finished. Thread ID is 4
Task 7 started. Thread ID is 4
Task 6 started. Thread ID is 5
Task 4 finished. Thread ID is 2
Task 8 started. Thread ID is 2
Task 7 finished. Thread ID is 4
Task 1 started. Thread ID is 3
Task 2 started. Thread ID is 2
Task 3 started. Thread ID is 4
Task 2 finished. Thread ID is 2
Task 4 started. Thread ID is 2
Task 1 finished. Thread ID is 3
Task 5 started. Thread ID is 3
Task 0 finished. Thread ID is 5
Task 3 finished. Thread ID is 4
Task 7 started. Thread ID is 4
Task 6 started. Thread ID is 5
Task 4 finished. Thread ID is 2
Task 8 started. Thread ID is 2
Task 7 finished. Thread ID is 4
Task 1 finished. Thread ID is 3
Task 5 started. Thread ID is 3
Task 0 finished. Thread ID is 5
Task 3 finished. Thread ID is 4
Task 7 started. Thread ID is 4
Task 6 started. Thread ID is 5
Task 4 finished. Thread ID is 2
Task 8 started. Thread ID is 2
Task 7 finished. Thread ID is 4
Task 0 finished. Thread ID is 5
Task 3 finished. Thread ID is 4
Task 7 started. Thread ID is 4
Task 6 started. Thread ID is 5
Task 4 finished. Thread ID is 2
Task 8 started. Thread ID is 2
Task 7 finished. Thread ID is 4
Task 6 started. Thread ID is 5
Task 4 finished. Thread ID is 2
Task 8 started. Thread ID is 2
Task 7 finished. Thread ID is 4
Task 7 finished. Thread ID is 4
Task 9 started. Thread ID is 4
Task 6 finished. Thread ID is 5
Task 5 finished. Thread ID is 3
Task 8 finished. Thread ID is 2
Task 9 finished. Thread ID is 4
All tasks completed.