)
并发支持库学习本节知识应在学习了 Linux 多线程之后对于进程线程的基础可以去我的 Linux 专栏进行学习哦本章节重点讲解库的使用不会涉及进程线程相关概念及基础知识。1. thread 库相关文档std::thread - cppreference.com 和 thread - C Reference。特点thread库底层是对各操作系统线程库的封装例如 Linux 下的pthread库和 Windows 下的线程库等。C11thread库的特点包括跨平台性通过条件编译实现以及面向对象设计。它融合了 C11 语言特性如右值引用的移动语义、可变参数模板等使用更加便捷。我们可以对比一下两个平台原生的线程接口它们的用法各不相同而通过 C11 封装后的线程库我们可以很方便地使用同一套接口完成跨平台开发。线程创建:下面thread 库提供了4 个构造函数日常开发中最常用的是第二个。它支持直接传入可调用对象 任意参数相比pthread_create不再局限于 C 语言式的函数指针参数传递也更加简洁方便。使用pthread_create时如果需要传递多个参数必须将参数封装成结构体再传递结构体指针。线程的创建也可以使用第一个和第四个构造函数配合完成通过右值线程对象的移动构造或移动赋值将线程对象转移给另一个线程对象管理。第三个构造函数表明线程对象不支持拷贝只能移动不能复制。join 操作join是主线程结束前需要阻塞等待创建的从线程否则主线程结束时进程也随之结束从线程可能还在运行就被强行终止。线程 IDthread::id是thread的内部类专门用于表示线程 ID。它支持大小比较、流插入与流提取操作还可以通过特化的hash仿函数作为键值用于unordered_map和unordered_set等容器。从底层实现来看thread本质是对不同操作系统线程库接口的封装。由于各平台的线程 ID 底层类型不一致因此必须通过一个类进行统一封装。获取方式线程对象可通过get_id()获取自身线程 ID在线程执行体内部可以通过this_thread::get_id()获取当前线程的 ID。Cstd::thread构造函数默认构造thread() noexcept;默认构造的std::thread对象不表示任何线程处于未初始化状态。初始化构造template class Fn, class... Args explicit thread (Fn fn, Args... args);用于创建并启动线程传入一个可调用对象fn和参数args...。这是最常用的构造方式支持多种可调用对象如函数指针、lambda 表达式等和参数的传递。拷贝构造删除thread (const thread) delete;std::thread不支持拷贝构造因为线程对象不能被复制。移动构造万能引用/引用折叠thread (thread x) noexcept;支持移动构造允许将一个线程对象的所有权转移给另一个线程对象。移动赋值万能引用/引用折叠thread operator (thread rhs) noexcept;支持移动赋值允许将一个线程对象的所有权转移给另一个线程对象。阻塞等待线程完成void join();主线程调用join方法阻塞等待子线程完成否则主线程结束时子线程可能被强制终止。对比pthread和 Windows 线程创建pthread线程创建int pthread_create(pthread_t *tidp, const pthread_attr_t *attr, void *(*start_rtn)(void*), void *arg);pthread_create需要传入线程函数指针start_rtn和参数arg。若需要传递多个参数需将参数打包成一个结构体并通过指针传递。线程创建后需要手动管理线程的同步和资源。Windows 线程创建HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId );CreateThread是 Windows 线程创建函数需要传入线程函数指针lpStartAddress和参数lpParameter。同样若需要传递多个参数需将参数打包成一个结构体并通过指针传递。线程创建后需要手动管理线程句柄和资源。总结std::thread是 C11 引入的线程库封装了底层线程库如pthread和 Windows 线程 API提供了跨平台的线程管理功能。它支持多种构造方式特别是通过模板参数支持更灵活的参数传递相比pthread和 Windows 线程创建std::thread更简洁且易于使用。std::thread不支持拷贝构造但支持移动构造和移动赋值这使得线程对象的所有权可以安全转移。主线程可以通过join方法阻塞等待子线程完成以避免子线程被强制终止。我们可以通过上面的知识点进行整合应用#include iostream #include thread // 多线程的使用 // 从 i 开始打印, 直到n为止 void Print(int n, int i) { for(; i n; i) { // 在执行体里面想要获取自己对应的线程id是需要使用this_thread::get_id() // 本质上就是调用一个全局函数该函数就被封装到了一个this_thread的命名空间里面 std::cout [ std::this_thread::get_id() ] : i std::endl; } } int main() { std::cout 主线程 [ std::this_thread::get_id() ] std::endl; std::thread t1(Print, 10, 1); std::thread t2(Print, 20, 11); std::cout t1 线程 [ t1.get_id() ] std::endl; std::cout t2 线程 [ t2.get_id() ] std::endl; t1.join(); t2.join(); return 0; }我们发现运行起来两个线程是有比较强的随机性的打印这是因为双方互相竞争控制台打印的结果我们后续学习相关的加锁互斥条件变量就可以有效的进行控制进行资源按照我们的规定进行分配lfzU22:~/WorkSpace/myLearn/thread$ ./main 主线程 [131602062537664] t1 线程 [131602055165504] [131602055165504] : 1 [131602055165504] : 2 [131602046772800] : 11 [131602046772800] : 12 [131602046772800] : 13 [131602046772800] : 14 [131602046772800] : 15 [131602046772800] : 16 t2 线程 [131602046772800] [131602055165504] : 3 [131602046772800] : 17 [131602055165504] : 4 [131602046772800] : 18 [131602055165504] : 5 [131602046772800] : 19 [131602055165504] : 6 [131602046772800] : 20 [131602055165504] : 7 [131602055165504] : 8 [131602055165504] : 9 [131602055165504] : 10 lfzU22:~/WorkSpace/myLearn/thread$ ./main 主线程 [136818042102720] [136818034996800] : 1 [136818034996800] : 2 [136818034996800] : 3 [136818034996800] : 4 [136818034996800] : 5 [136818034996800] : 6 [136818034996800] : 7 [136818034996800t1 线程 [] : 1368180349968008 [136818034996800] : 9 [136818034996800] : 10 [136818026604096] : 11 [136818026604096] : 12 [136818026604096] : 13 [136818026604096] : 14 [136818026604096] : 15 [136818026604096] : 16 [136818026604096] : 17 [136818026604096] : 18 [136818026604096] : 19 [136818026604096] : 20 ] t2 线程 [136818026604096]我们可以看到有的会黏到一块这就是同时访问共享资源的安全问题2. this_threadthis_thread - C Referencethis_thread是一个命名空间主要封装了4 个与当前线程相关的全局接口函数。get_id获取当前执行线程的线程 ID。yield主动让出当前线程的执行权让其他线程优先运行。该函数的具体行为依赖于操作系统的调度器机制与系统状态。例如在 Linux 的SCHED_FIFO先进先出实时调度策略中会将当前线程挂起并放到同优先级就绪线程队列的尾部若同优先级下无其他等待线程则yield不会产生效果。线程的执行本质是排队竞争 CPUyield的作用就是线程已经排到 CPU却主动将自己放回同优先级队列末尾重新排队。该接口常用于无锁编程。sleep_for阻塞当前线程至少休眠指定的相对时长。由于系统调度、资源竞争等延迟实际阻塞时间可能长于设定值。例如休眠 3ms。sleep_until阻塞当前线程直到抵达指定的绝对时间点。受系统调度影响实际唤醒时间可能晚于设定的时间点。例如从当前时间休眠到 10:10。针对这个 yield 是如何体现无锁的我们来简单说一下yield 在无锁编程中的核心作用体现在当多个线程竞争原子变量时若当前线程竞争失败不会使用互斥锁进行阻塞等待而是通过yield主动让出 CPU 时间片让其他占用资源的线程优先执行从而尽快完成操作。整个过程不使用锁、不进入阻塞状态、不依赖操作系统的互斥机制完全在用户态完成线程调度。这种“竞争失败则礼让不阻塞、不挂起、不锁等待”的设计正是无锁编程的核心思想。因此yield是无锁模型中实现高效等待、避免 CPU 空转的关键接口也真正体现了无锁的设计理念。所以使用无锁编程的核心原因是传统互斥锁会带来线程阻塞、内核态切换、竞争延迟、死锁风险等性能与稳定性问题而无锁方案基于原子操作实现线程竞争失败时不会阻塞挂起只需通过yield主动让出 CPU 并重试全程在用户态执行能大幅提升高并发场景下的执行效率同时避免死锁满足低延迟、高吞吐的系统需求。不过无锁编程仅适用于高并发、低延迟、简单数据结构如队列、栈它依赖原子操作编写难度极高容易出现隐藏极深的并发 bug且无法处理复杂的业务逻辑而锁虽然存在性能开销但使用简单、逻辑清晰、通用性极强能安全保护任意复杂的共享资源满足绝大多数业务场景的开发需求。因此工程上是锁为主无锁为辅而非完全替代。所以我们不要丢弃锁下面我们来看看几个比较常用的场景吧场景 1高并发无锁日志系统高并发日志是后端最常见的无锁场景多线程同时写日志不能加锁否则会导致大量线程阻塞、IO 抖动、服务变慢。无锁日志利用原子状态 yield实现不阻塞、不丢日志、高吞吐。#include iostream #include thread #include vector #include atomic using namespace std; // 自旋锁日志器不是无锁 class SpinLockLogger { public: void log(const string msg) { // 只需要 acquire不需要 acq_rel while (!_can_write.exchange(false, memory_order_acquire)) { this_thread::yield(); } // 临界区唯一允许访问cout cout [thread- this_thread::get_id() ] msg \n; _can_write.store(true, memory_order_release); } private: atomicbool _can_write{true}; }; void test_log_system() { SpinLockLogger logger; vectorthread threads; for (int i 0; i 100; i) { threads.emplace_back([]() { logger.log(这是一条高并发日志); }); } for (auto t : threads) t.join(); } int main() { test_log_system(); return 0; }场景 2高频数据采集 / 上报无锁队列监控指标、埋点上报、客户端数据采集要求极高吞吐、极低延迟。不能加锁否则会导致数据上报延迟。无锁队列是工业界标准方案。template class T class LockFreeQueue { private: // 链表节点 struct Node { T data; Node* next nullptr; Node(const T val) : data(val) {} }; public: // 生产者无锁入队 void push(const T val) { // 新建节点 Node* new_node new Node(val); while (true) { // 读取队尾原子加载 Node* tail _tail.load(memory_order_acquire); // CAS 尝试将新节点挂到队尾 if (_tail.compare_exchange_weak(tail, new_node)) { // 队列为空时同步头指针 if (_head.load() nullptr) { _head.store(new_node); } break; } // 竞争失败 → 让出CPU this_thread::yield(); } } // 消费者无锁出队 bool pop(T* out) { while (true) { Node* head _head.load(memory_order_acquire); if (head nullptr) { return false; // 空队列 } // CAS 尝试弹出队头 if (_head.compare_exchange_weak(head, head-next)) { *out head-data; delete head; return true; } // 竞争失败 → 让出CPU this_thread::yield(); } } private: atomicNode* _head nullptr; atomicNode* _tail nullptr; }; // 测试数据采集 上报 void test_data_collector() { LockFreeQueueint queue; // 生产者线程采集数据 thread producer([]() { for (int i 1; i 5; i) { queue.push(i); printf(采集数据%d\n, i); } }); // 消费者线程上报数据 thread consumer([]() { int data; while (queue.pop(data)) { printf(上报数据%d\n, data); } }); producer.join(); consumer.join(); }场景 3游戏帧同步 / 网络服务器无锁指令队列游戏帧同步、网络服务器不能加锁否则会卡顿、掉帧、延迟变高。所有网络消息、玩家操作都必须无锁投递、无锁执行。#include functional using Cmd functionvoid(); class GameFrameSync { public: // 无锁添加指令 void push_command(Cmd cmd) { // 自旋 yield 获取队列使用权 while (!_lock_flag.exchange(false, memory_order_acq_rel)) { this_thread::yield(); } _cmd_list.push_back(move(cmd)); // 释放 _lock_flag.store(true, memory_order_release); } // 执行一帧所有指令 void run_frame() { while (!_lock_flag.exchange(false, memory_order_acq_rel)) { this_thread::yield(); } // 执行所有玩家指令 for (auto cmd : _cmd_list) { cmd(); } _cmd_list.clear(); _lock_flag.store(true); } private: atomicbool _lock_flag true; vectorCmd _cmd_list; }; // 测试多线程发送游戏指令 void test_game_frame() { GameFrameSync frame; thread t1([]() { frame.push_command([]() { cout 玩家移动 endl; }); }); thread t2([]() { frame.push_command([]() { cout 释放技能 endl; }); }); t1.join(); t2.join(); // 执行一帧 frame.run_frame(); }场景 4中间件组件无锁异步任务队列RPC 框架、消息队列、异步任务调度器都需要无锁任务投递。加锁会导致吞吐量上不去。using Task functionvoid(); // 无锁任务队列 class LockFreeTaskQueue { public: // 无锁添加任务 void add_task(Task task) { // 等待任务被取走 while (_has_task.load(memory_order_acquire)) { this_thread::yield(); } _task move(task); _has_task.store(true, memory_order_release); } // 执行任务 bool run_task() { if (!_has_task.load(memory_order_acquire)) return false; // 执行任务 _task(); _has_task.store(false, memory_order_release); return true; } private: atomicbool _has_task false; Task _task; }; // 测试RPC/消息队列任务 void test_middleware_task() { LockFreeTaskQueue task_queue; // 工作线程不断尝试执行任务 thread worker([]() { while (!task_queue.run_task()) { this_thread::yield(); } }); // 主线程投递一个RPC调用任务 task_queue.add_task([]() { cout 执行RPC调用 / 消息处理 endl; }); worker.join(); }无锁编程不是为了替代锁而是专门解决高并发、低延迟、高性能场景。日志、数据采集、游戏、中间件、网络框架 → 用无锁业务逻辑、复杂操作、数据库 → 用锁yield的作用是竞争失败时主动让出 CPU不阻塞、不加锁、不进内核实现无锁协作式调度。还有关于时间的使用我们需要理解以下几种时间类型chrono - C Referencechrono是一个计时相关的类型。duration - C Reference 是用来管理一个相对时间段的类。time_point - C Reference 是用来管理一个绝对时间点的类。1.std::this_thread::sleep_fortemplate class Rep, class Period void sleep_for(const chrono::durationRep, Period rel_time);使当前线程暂停执行指定的时间间隔rel_time。时间间隔可以是秒、毫秒、微秒等。rel_time一个std::chrono::duration对象表示线程需要暂停的时间间隔。Rep表示时间间隔的数值类型例如int、double等。Period时间单位例如std::ratio1, 1秒、std::ratio1, 1000毫秒等。#include iostream // std::cout, std::endl #include thread // std::this_thread::sleep_for #include chrono // std::chrono::seconds int main() { std::cout countdown:\n; for (int i 10; i 0; --i) { std::cout i std::endl; std::this_thread::sleep_for(std::chrono::seconds(1)); } std::cout Lift off!\n; return 0; }该函数阻塞当前线程的执行至少持续指定的时间间隔。示例中通过std::chrono::seconds(1)指定每秒打印一次倒计时。2.std::this_thread::sleep_untiltemplate class Clock, class Duration void sleep_until(const chrono::time_pointClock, Duration abs_time);使当前线程暂停执行直到系统时间达到指定的绝对时间点abs_time。如果当前时间已经超过了abs_time线程会立即继续执行。abs_time一个std::chrono::time_point对象表示线程需要暂停到的时间点。Clock时钟类型例如std::chrono::system_clock或std::chrono::steady_clock。Duration时间间隔类型例如std::chrono::seconds、std::chrono::milliseconds等。#include iostream // std::cout #include iomanip // std::put_time #include thread // std::this_thread::sleep_until #include chrono // std::chrono::system_clock #include ctime // std::time_t, std::tm, std::localtime, std::mktime int main() { using std::chrono::system_clock; std::time_t tt system_clock::to_time_t(system_clock::now()); struct std::tm * ptm std::localtime(tt); std::cout Current time: std::put_time(ptm, %X) \n; std::cout Waiting for the next minute to begin...\n; ptm-tm_min; ptm-tm_sec 0; std::this_thread::sleep_until(system_clock::from_time_t(std::mktime(ptm))); std::cout std::put_time(ptm, %X) reached!\n; return 0; }该函数阻塞当前线程直到指定的绝对时间点到达。示例中计算了当前时间的下一分钟并通过std::this_thread::sleep_until等待到该时间点。时间计算示例#include iostream // std::cout #include chrono // std::chrono::duration, std::chrono::system_clock #include ctime // std::time_t, std::ctime int main() { using namespace std::chrono; durationint, std::ratio60 * 60 * 24 one_day(1); // 一天的时间间隔 system_clock::time_point today system_clock::now(); system_clock::time_point tomorrow today one_day; time_t tt; tt system_clock::to_time_t(today); std::cout today is: std::ctime(tt); tt system_clock::to_time_t(tomorrow); std::cout tomorrow will be: std::ctime(tt); return 0; }时间计算使用std::chrono::duration和std::chrono::system_clock来表示和计算时间间隔。示例中计算了当前时间和明天的时间点并通过std::ctime打印出来。这些代码展示了如何使用 C 的this_thread和chrono库来实现线程的暂停和时间计算功能。下一篇开启互斥锁 --- mutex