从pthread到std::jthread:一个C++老鸟的多线程编程进化史
从pthread到std::jthread一个C老鸟的多线程编程进化史记得第一次接触多线程编程是在2008年那时我刚从学校毕业加入了一家做网络设备的公司。我们的产品需要处理大量并发连接而当时的C标准库还没有原生线程支持。于是我开始了与pthread的漫长斗争。1. 石器时代与pthread共舞的日子在C11之前Linux下的多线程编程几乎等同于使用pthread库。那些年每个C程序员都要掌握一套特殊的生存技能#include pthread.h void* thread_func(void* arg) { int* num (int*)arg; printf(Thread received: %d\n, *num); return NULL; } int main() { pthread_t tid; int arg 42; pthread_create(tid, NULL, thread_func, arg); pthread_join(tid, NULL); return 0; }这段简单的代码背后隐藏着无数坑点类型安全缺失必须使用void*进行参数传递完全失去了C的类型检查优势资源管理困难忘记调用pthread_join会导致内存泄漏错误处理繁琐每个pthread函数调用后都要检查返回值跨平台差异不同Unix-like系统的实现细节有微妙差别最让我记忆深刻的是2010年的一次生产事故。当时我们有一个服务因为忘记pthread_join导致线程资源泄漏运行两周后就耗尽了系统资源。那次事故让我写了整整三天的核心转储分析报告。2. 工业革命C11带来的std::thread曙光当C11标准发布时std::thread就像黑暗中的一束光。对比之前的pthread它带来了几个革命性的改进特性pthreadstd::thread类型安全无使用void*有模板参数资源管理手动基于RAII错误处理返回错误码抛出异常可移植性仅限于POSIX系统跨平台函数对象支持仅支持C函数支持lambda和函数对象第一次用std::thread重构旧代码时的快感至今难忘#include thread #include iostream void worker(int value) { std::cout Thread got: value std::endl; } int main() { int value 42; std::thread t(worker, value); t.join(); return 0; }但std::thread并非完美。在大型项目中我们很快发现了新的挑战线程生命周期管理如果忘记join或detach程序会terminate异常安全线程函数抛出异常时处理起来很棘手线程取消没有标准化的线程停止机制2015年我在一个高并发交易系统中就踩到了大坑。当时我们需要动态调整工作线程数量但发现优雅停止线程几乎是不可能的任务。最终我们不得不实现了一套基于条件变量的复杂停止机制。3. 智能时代C20的std::jthread革命C20引入的std::jthread解决了std::thread最痛的两个问题自动资源管理析构时自动join再也不用担心忘记join导致的问题内置停止机制通过stop_token实现线程间协作式停止3.1 stop_token的魔法stop_token机制的核心思想是协作式取消而非强制终止。看看这个典型的生产者-消费者模式实现#include thread #include iostream #include queue #include mutex #include condition_variable void producer(std::stop_token stop, std::queueint q, std::mutex mtx, std::condition_variable cv) { for(int i 0; !stop.stop_requested(); i) { { std::lock_guard lock(mtx); q.push(i); } cv.notify_one(); std::this_thread::sleep_for(std::chrono::milliseconds(100)); } } void consumer(std::stop_token stop, std::queueint q, std::mutex mtx, std::condition_variable cv) { while(!stop.stop_requested()) { std::unique_lock lock(mtx); cv.wait(lock, stop, [q]{ return !q.empty(); }); if(stop.stop_requested()) break; int val q.front(); q.pop(); lock.unlock(); std::cout Consumed: val std::endl; } } int main() { std::queueint q; std::mutex mtx; std::condition_variable cv; std::jthread prod(producer, std::ref(q), std::ref(mtx), std::ref(cv)); std::jthread cons(consumer, std::ref(q), std::ref(mtx), std::ref(cv)); std::this_thread::sleep_for(std::chrono::seconds(1)); // 无需手动join析构时会自动处理 return 0; }这个例子展示了std::jthread的几个关键优势自动资源清理即使发生异常jthread也会确保线程正确join优雅停止通过stop_token检查停止请求线程可以安全清理资源条件变量集成condition_variable支持stop_token感知的等待3.2 性能与取舍std::jthread不是免费的午餐它带来的便利性是有代价的每个jthread对象需要额外存储stop_statestop_token检查会引入轻微运行时开销比std::thread占用更多内存通常多16-32字节在极端性能敏感的场景你可能还需要回归到std::thread甚至pthread。但在90%的应用中jthread的开销完全可以接受。4. 实战现代化线程池设计结合这些年积累的经验我总结出了一个现代化的线程池实现模式class ThreadPool { public: explicit ThreadPool(size_t num_threads) { for(size_t i 0; i num_threads; i) { workers_.emplace_back([this](std::stop_token st) { while(!st.stop_requested()) { std::functionvoid() task; { std::unique_lock lock(queue_mutex_); queue_cv_.wait(lock, st, [this]{ return !tasks_.empty(); }); if(st.stop_requested()) return; task std::move(tasks_.front()); tasks_.pop(); } task(); } }); } } ~ThreadPool() { for(auto worker : workers_) { worker.request_stop(); } queue_cv_.notify_all(); } templatetypename F void enqueue(F f) { { std::lock_guard lock(queue_mutex_); tasks_.emplace(std::forwardF(f)); } queue_cv_.notify_one(); } private: std::vectorstd::jthread workers_; std::queuestd::functionvoid() tasks_; std::mutex queue_mutex_; std::condition_variable_any queue_cv_; };这个实现有几个值得注意的特点异常安全使用jthread确保线程总会正确join优雅关闭析构时请求所有线程停止停止感知条件变量配合stop_token工作通用任务使用std::function支持任意可调用对象在最近的一个数据处理项目中这个线程池设计帮助我们减少了约40%的线程管理代码同时显著提高了系统的可靠性。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2523443.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!