自实现线程池

这个项目采用自实现线程池,相比于qt的全局线程池,这个线程池可以直接获取到返回的future结果,同时支持线程命名。

安全队列

cpp 复制代码
struct SafeQueue {
    bool empty() {
        std::shared_lock<std::shared_mutex> const lock(_mutex);
        return _queue.empty();
    }

    size_t size() {
        std::shared_lock<std::shared_mutex> lock(_mutex);
        return _queue.size();
    }
    void push(T &ele) {
        std::unique_lock<std::shared_mutex> const lock(_mutex);
        _queue.push(ele);
    }
    bool pop(T &ele) {
        std::unique_lock<std::shared_mutex> const lock(_mutex);
        if (_queue.empty()) {
            return false;
        }
        ele = std::move(_queue.front());
        _queue.pop();
        return true;
    }

设置线程名

windows平台

寻找系统的kernel123模块,寻找设置模块中设置线程名的函数的地址,如果找到了就就这个函数地址转换为可调用的函数,设置线程名。防止有些较老的Windows系统没有这个函数。

Linux平台

强制保持线程名长度在15以下

cpp 复制代码
static void setThreadName(std::string_view name) {
#ifdef _WIN32
    HMODULE kernel32_module = ::GetModuleHandleW(L"kernel32.dll");
    if (kernel32_module) {
        using SetThreadDescriptionFunc = HRESULT(WINAPI *)(HANDLE, PCWSTR);
        auto proc = GetProcAddress(kernel32_module, "SetThreadDescription");
        auto set_thread_description_func = reinterpret_cast<SetThreadDescriptionFunc>(reinterpret_cast<void *>(proc));

        if (set_thread_description_func) {
            std::wstring const w_name = CommonTool::charToWchar(name);
            set_thread_description_func(::GetCurrentThread(), w_name.c_str());
        }
    }
#elif __linux__
    if (name) {
        // Linux平台设置线程名称,限制长度为15个字符+终止符
        std::string threadName(name);
        if (threadName.length() > 15) {
            threadName.resize(15);
        }
        pthread_setname_np(pthread_self(), threadName.c_str());
    }
#endif
}

原子操作

原子变量atomic有两个安全操作函数

std::automic<bool> temp=false;

store()安全写入temp.store(true);

load()安全读取temp.load(memory_order_...);

两个函数都可以结合memory_order_参数

  • memory_order_relaxed:不保证顺序,只保证原子性,最快,但线程间可能看不到最新值。
  • memory_order_acquire:用于"读操作",保证本线程后续的所有读写都不会被重排到这个 load 之前。常用于同步"读取"。
  • memory_order_release:用于"写操作",保证本线程之前的所有读写都不会被重排到这个 store 之后。常用于同步"写入"。
  • memory_order_acq_rel:同时用于读写,结合 acquire 和 release 的语义。
  • memory_order_seq_cst:最严格的顺序,保证全局一致性(默认)。

线程池主循环

设置线程名worker+index,有一个原子变量,默认为false,如果为false就持续运行。获取线程池的锁,当条件变量判断线程池的标志为false或者任务队列不为空的时候唤醒,避免忙等待。当成功从任务队列取到一个任务的时候,执行这个任务。

cpp 复制代码
void operator()() noexcept {
            setThreadName(std::string("worker--" + std::to_string(_index)));
            bool flag = false;
            std::function<void()> func;
            while (!_pool->_isShutdown.load(std::memory_order_acquire)) {
                {
                    std::unique_lock<std::mutex> lock(_pool->_mutex);
                    _pool->_cv.wait(lock, [this]() {
                        return this->_pool->_isShutdown.load(std::memory_order_acquire) ||
                               !this->_pool->_taskQueue.empty();
                    });
                    flag = _pool->_taskQueue.pop(func);
                }
                if (flag) {
                    func();
                }
            }
        }

线程池构造函数

用cpu核心数设置n,将n传给线程池数组初始化数组,循环将数组中的每一个元素初始化为线程对象并绑定私有类worker操作,这样每次创建一个线程会执行woker的循环任务像设置线程名加入任务等。

cpp 复制代码
explicit ThreadPool(uint32_t n = std::thread::hardware_concurrency()) : _threads(n) {
        static uint32_t index = 0;
        for (auto &thread : _threads) {
            thread = std::thread(Worker(this, index++));
        }
        spdlog::info("ThreadPool init with {} threads", n);
    }

线程池析构函数

停止线程池运转,唤醒所有线程,如果可以回收,循环回收所有线程。

cpp 复制代码
~ThreadPool() noexcept {
        _isShutdown.store(true, std::memory_order_release);
        _cv.notify_all();
        for (auto &thread : _threads) {
            if (thread.joinable()) {
                thread.join();
            }
        }
    }

提交任务函数

定义一个模板函数,接受任意类型函数以及任意数量参数,指定返回值类型为std::future,decltype自动推导传入函数类型。用std::forward将任务函数完美转发给lamda使用,类似移动语义。将任务函数包装成packaged_task,再将任务对象转换成可进入线程池的function<void()>类型,加入到任务队列,返回任务的future,方便后续访问结果。

cpp 复制代码
template <typename F, typename... Args>
    auto submit(F &&function, Args &&...args) -> std::future<decltype(function(args...))> {
        std::function<decltype(function(args...))()> func = [new_function = std::forward<F>(function), args...]() {
            return new_function(args...);
        };
        // 使用packaged_task获取函数签名,链接func和future,使之能够进行异步操作
        auto task_ptr = std::make_shared<std::packaged_task<decltype(function(args...))()>>(func);
        // 包装可进入线程的线程函数
        std::function<void()> wrapper_func = [task_ptr]() { (*task_ptr)(); };
        {
            const std::unique_lock<std::mutex> lock(_mutex);

            // 将包装的函数加入队列
            _taskQueue.push(wrapper_func);
        }

        // 唤醒一个线程
        _cv.notify_one();
        // 返回前面注册的任务指针
        return task_ptr->get_future();
    }
};
相关推荐
老王熬夜敲代码2 小时前
C++新特性:string_view
开发语言·c++·笔记
ouliten3 小时前
石子合并模型
c++·算法
weixin_461769403 小时前
5. 最长回文子串
数据结构·c++·算法·动态规划
散峰而望3 小时前
【算法竞赛】C++入门(三)、C++输入输出初级 -- 习题篇
c语言·开发语言·数据结构·c++·算法·github
不会c嘎嘎3 小时前
数据结构 -- 常见的八大排序算法
数据结构·c++·算法·排序算法·面试题·快速排序
REDcker3 小时前
C++ 崩溃堆栈捕获库详解
linux·开发语言·c++·tcp/ip·架构·崩溃·堆栈
WW_千谷山4_sch3 小时前
洛谷P8653:[模板] [蓝桥杯 2017 国 C] 分考场(染色最小色数)
c++·算法·蓝桥杯·深度优先
兵哥工控3 小时前
MFC高精度方波发生器实现
c++·mfc
汉克老师4 小时前
GESP2025年12月认证C++五级真题与解析(判断题1-10)
c++·链表·贪心算法·排序·gesp5级·gesp五级