自实现线程池

这个项目采用自实现线程池,相比于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();
    }
};
相关推荐
Let's Chat Coding1 小时前
身份识别、身份认证与授权的区别
c++
鹏易灵2 小时前
C++——8.移动语义初探(移动构造、移动赋值)
开发语言·c++·php
凯瑟琳.奥古斯特2 小时前
力扣1008:前序重建BST
开发语言·c++·算法·leetcode·职场和发展
Alvin's Tech Blog2 小时前
内存泄漏会导致操作系统永久丢失内存吗?它会导致应用程序崩溃吗?
c++·内存管理·内存泄漏
会周易的程序员2 小时前
使用 LuaBridge 封装 C++ 日志库 microLog 为 Lua 模块
c++·物联网·junit·lua·日志·iot·aiot
wbs_scy3 小时前
仿 muduo 高并发服务器项目:从 timerfd 到时间轮实现定时任务机制
linux·服务器·c++
Darkwanderor3 小时前
Linux进程优先级操作
linux·运维·c语言·c++
智者知已应修善业4 小时前
【P12159蓝桥杯数组翻转】2025-4-24
c语言·c++·经验分享·笔记·算法·蓝桥杯
盐焗鹌鹑蛋4 小时前
【C++】多态
java·jvm·c++
会周易的程序员5 小时前
microLog 的 log_reader 架构解析:嵌入式日志检索引擎的设计之道
c++·物联网·架构·日志·iot·aiot