使用C++20的信号量来替代条件变量,
使用 impl 模式来隐藏具体的实现,可减少编译时间以及隐藏具体的细节实现。
PS:
- 任务队列如果使用无锁队列的话效率应该会有所提高。
- Impl模式可以隐藏数据成员以及实现,但是由于C++编译时需要确定类型的大小,所以使用智能指针。
头文件(接口)
ThreadPool.h
:
#pragma once
#include <cstddef>
#include <memory>
#include <functional>
class ThreadPool {
private:
void _enqueue(std::function<void()> task);
public:
ThreadPool(const std::size_t threadnum);
~ThreadPool();
template <typename Func, typename...Args>
inline void enqueue(Func &&f, Args&&...args) noexcept {
return _enqueue(std::bind(std::forward<Func>(f), std::forward<Args>(args)...));
}
private:
// 使用 impl 来隐藏具体的实现,可减少编译时间以及隐藏具体的细节实现
class Impl;
std::unique_ptr<Impl> m_handle;
};
源文件(实现)
ThreadPool.cpp
:
#include <cstddef>
#include <atomic>
#include <memory>
#include <semaphore>
#include <vector>
#include <thread>
#include <mutex>
#include "ThreadPool.h"
constexpr unsigned EACH_THREAD_NUMS { 512 };
class ThreadPool::Impl {
class Tasks;
private:
std::unique_ptr<Tasks> m_tasks;
// 用于管理空闲槽位
std::counting_semaphore<> m_free_slots;
// 用于管理可用任务
std::counting_semaphore<> m_available_tasks{0};
// 工作线程
std::vector<std::thread> m_workers;
// 是否停止
std::atomic<bool> m_stop{false};
private:
void work_func();
public:
Impl(const unsigned thread_nums);
~Impl();
void enqueue(std::function<void()> &&task);
};
class ThreadPool::Impl::Tasks {
private:
const unsigned m_nums;
std::vector<std::function<void()>> m_tasks;
unsigned m_start{0};
unsigned m_end{0};
unsigned m_count{0};
std::mutex m_lock;
public:
Tasks(const unsigned nums);
~Tasks() = default;
bool push(std::function<void()> &&in);
bool pop(std::function<void()> &out);
};
ThreadPool::ThreadPool(const unsigned threadnum)
: m_handle(std::make_unique<Impl>(threadnum)) {
;
}
ThreadPool::~ThreadPool() {
;
}
void ThreadPool::_enqueue(std::function<void()> task) {
return m_handle->enqueue(std::move(task));
}
void ThreadPool::Impl::work_func() {
while (true) {
std::function<void()> task;
m_available_tasks.acquire(); // 等待有任务
if (!m_tasks->pop(task)) {
if (m_stop) {
return;
}
continue;
}
m_free_slots.release();
if (task) {
task();
}
}
}
ThreadPool::Impl::Impl(const unsigned thread_nums)
: m_tasks(std::make_unique<Tasks>(thread_nums * EACH_THREAD_NUMS)), m_free_slots(thread_nums * EACH_THREAD_NUMS) {
for (unsigned i { 0 }; i < thread_nums; ++i) {
m_workers.emplace_back(&ThreadPool::Impl::work_func, this);
}
}
ThreadPool::Impl::~Impl() {
m_stop = true;
// +256是为了防止虚假唤醒
for (unsigned i = 0; i < m_workers.size() + 256; ++i) {
m_available_tasks.release();
}
for (std::thread &worker : m_workers) {
if (worker.joinable()) {
worker.join();
}
}
}
void ThreadPool::Impl::enqueue(std::function<void()> &&task) {
if (m_stop) {
return;
}
m_free_slots.acquire();
if (!m_tasks->push(std::move(task))) {
return;
}
m_available_tasks.release();
}
ThreadPool::Impl::Tasks::Tasks(const unsigned nums)
: m_nums(nums) {
m_tasks.resize(nums);
}
bool ThreadPool::Impl::Tasks::push(std::function<void()> &&in) {
std::lock_guard<std::mutex> lock(m_lock);
if (m_nums == m_count) {
return false;
}
m_tasks[m_end] = std::move(in);
m_end = (m_end + 1) % m_nums;
++m_count;
return true;
}
bool ThreadPool::Impl::Tasks::pop(std::function<void()> &out) {
std::lock_guard<std::mutex> lock(m_lock);
if (0 == m_count) {
return false;
}
out = std::move(m_tasks[m_start]);
m_start = (m_start + 1) % m_nums;
--m_count;
return true;
}
测试案例
test.cpp
:
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <iostream>
#include <thread>
#include <atomic>
#include "ThreadPool.h"
static std::atomic<std::uint64_t> s_count;
void test_threadfunc(const int idx) {
const std::size_t count{ s_count++ };
for (int i { 0 }; i < 10; ++i) {
// std::printf("[%llu]: Hello\n", count);
}
// std::printf("[%llu]: Hello\n", count);
}
int main() {
try {
const size_t threadCount = 3000; // 线程数量
do {
ThreadPool pool(threadCount);
// 提交一些任务
for (int i { 0 }; i < 65536; ++i) {
pool.enqueue(test_threadfunc, i);
}
std::this_thread::sleep_for(std::chrono::seconds(5));
} while (0);
std::cout << "count: " << s_count << std::endl;
}
catch (const std::exception &e) {
std::cout << "Exception: " << e.what() << std::endl;
}
return 0;
}