cpp多线程\进程学习

📅 2026/8/13 20:53:29
cpp多线程\进程学习
1.threadstd::thread是 C11 引入的跨平台线程管理类封装了操作系统的线程 API如 pthread、Windows 线程提供统一的线程操作接口。线程的生命周期由join()和detach()控制。thread在创建时就开始执行join()join () 会让调用该方法的线程通常是主线程阻塞在调用 join () 的位置直到子线程 完整执行完毕调用线程才会解除阻塞继续执行join()之后的代码。detach()分离线程后台运行无需等待。线程结束后自动释放资源无法再join。joinable()检查线程是否可连接get_id()获取线程 IDthis_thread::get_id获取当前线程idswap()交换线程所有权线程不变交换句柄2.mutexmutex用于保证线程安全属于互斥锁直接调用lock()/unlock()易导致死锁如临界区抛出异常未解锁。互斥锁std::mutex本身没有主动 “通知” 等待线程的机制但等待线程可以通过被动感知锁的状态变化来获取锁已释放的信息依赖操作系统的 “唤醒机制”。C 提供 RAII资源获取即初始化包装类自动管理锁的生命周期保证异常安全std::lock_guard构造时加锁析构时解锁不可手动解锁。std::unique_lock构造时可选延迟加锁std::defer_lock适用于多锁协调场景防止死锁支持手动解锁、转移锁所有权。互斥锁等不及就休眠自旋锁死等不放cpp自旋锁依赖于atomiccompare_exchange_weak实现3.condition_variablestd::condition_variable是线程间通信的工具允许一个或多个线程等待某个条件满足由其他线程通知唤醒避免无效的轮询等待。与unique_lock结合使用wait(lock)阻塞当前线程并释放锁直到被notify_one()/notify_all()唤醒并满足条件自动释放锁否则继续阻塞。wait(lock, predicate)带谓词的等待仅当predicate()返回true时唤醒避免虚假唤醒。谓词predicate的作用是在被notify唤醒后检查条件是否满足其内部等价于下述代码while (!predicate()) { wait(lock); // 释放锁并阻塞直到被唤醒 }notify_one()从等待队列中随机唤醒一个线程notify_all()唤醒等待队列中所有的线程多个线程竞争4.Semaphore包括counting_semaphore、binary_semaphore两个信号量具有方法 acquire信号量-1try_acquire允许失败不阻塞release信号量1#include chrono #include iostream #include semaphore #include thread std::binary_semaphore //std::counting_semaphore1 smphSignalMainToThread{0}, smphSignalThreadToMain{0}; void ThreadProc() { smphSignalMainToThread.acquire(); std::cout [thread] Got the signal\n; // response message using namespace std::literals; std::this_thread::sleep_for(3s); std::cout [thread] Send the signal\n; // message smphSignalThreadToMain.release(); } int main() { std::thread thrWorker(ThreadProc); std::cout [main] Send the signal\n; // message smphSignalMainToThread.release(); smphSignalThreadToMain.acquire(); std::cout [main] Got the signal\n; // response message thrWorker.join(); }5.atomicstd::atomicT是模板类用于定义原子类型确保对其的操作是原子的不可分割避免多线程访问时的数据竞争实现无锁编程但仅适用于int等大多数基本类型也可以应用于大多数指针atomicT *CAS是其核心硬件基础。std::atomicT要求类型T是 “可平凡复制”trivially copyable的。即T不能有自定义的复制构造函数、移动构造函数、赋值运算符且其内存布局可通过原始字节拷贝如memcpy复制。除了原子类型的一些运算重载还有原子操作 load、store、fetch_add、feach_sub可以搭配memory_order使用定义内存序常见方法loadint rk.load(memory_order_acquire);storek.store(1,memory_order_release);compare_exchange_weaklock_flag.compare_exchange_weak(expect, true, acquire, relaxed)适用于非强竞争的场景// 极简自旋锁完全依赖std::atomic实现 class SpinLock { private: // 1. 用atomicbool做锁标记普通bool会有线程安全问题 std::atomicbool lock_flag{false}; // false未锁true已锁 public: // 加锁自旋抢锁依赖atomic的CAS原子操作 void lock() { bool expect false; // CAS原子地检查并修改锁标记失败则循环自旋 while (!lock_flag.compare_exchange_weak(expect, true)) { expect false; // 重置期望准备下次重试 } } // 解锁依赖atomic的原子写操作 void unlock() { lock_flag.store(false); // 原子重置锁标记避免多线程混乱 } };6.memory_orderatomic保证操作的原子性memory_order保证可见性和顺序性memory_order是 C11 引入的枚举类型是原子变量执行操作时的行为规则用于控制原子操作std::atomic的内存可见性和指令重排序约束是无锁编程中保证多线程数据一致性的核心机制。memory_order_seq_cst默认顺序一致性最严格的约束所有线程看到的原子操作顺序完全一致但性能开销大memory_order_relaxed宽松操作不施加任何同步或排序约束于其他读写仅保证此操作的原子性memory_order_acquire获取语义当前线程中原子操作之后的所有读操作不会被重排序到原子操作之前且能看到其他线程对同一原子变量执行release操作前的所有写入。memory_order_release释放语义当前线程中原子操作之前的所有写操作不会被重排序到原子操作之后且这些写入对执行acquire操作的线程可见。内存可见性memory_order或mutex包括两个完全不同层级的 “缓存问题”—— 一个是编译器层面的 “寄存器缓存”一个是硬件层面的 “CPU 缓存L1/L2/L3一致性”而 Cvolatile只盯着前者对后者完全 “视而不见”。C 中的volatile并非为多线程同步设计这是最关键的认知其核心作用是约束编译器对 “易变内存” 的优化行为。程序与硬件 / 外部信号交互的场景天然不会触发「跨核心的 CPU 缓存一致性问题」7.moodycamelConcurrentQueueConcurrentQueue是一个第三方无锁队列它专为多线程环境设计允许多个线程同时进行入队、出队操作而无需开发者手动实现同步机制从而简化了多线程数据共享的复杂度。使用以下四个方法实现线程安全ConcurrentQueue(size_t initialSizeEstimate)构造函数初始化队列大小enqueue(T item)入队若空间不足会分配额外空间try_enqueue(T item)尝试入队若有空间入队成功返回truetry_dequeue(T item)尝试出队item出队值出队成功返回truemsvc提供了默认的concurrent_queue gcc中可使用moodycamel::ConcurrentQueue。以有界缓冲区为例理解mutex和condition_variable的用法若将队列替换为ConcurrentQueue则无需mutex和condition_variable并且可以实现并发。同样也可以通过原子操作切换为无锁#include queue #include mutex #include condition_variable templatetypename T class BoundedBuffer { public: explicit BoundedBuffer(size_t capacity) : capacity_(capacity) { if (capacity 0) { throw std::invalid_argument(Buffer capacity must be greater than 0); } } // 入队操作阻塞直到有空间 void push(T element) { std::unique_lockstd::mutex lock(mtx_); // 等待缓冲区不满处理虚假唤醒 not_full_.wait(lock, [this] { return queue_.size() capacity_; }); queue_.push(std::move(element)); // 使用移动语义避免拷贝 not_empty_.notify_one(); // 通知可能等待的出队操作 } // 出队操作阻塞直到有元素 T pop() { std::unique_lockstd::mutex lock(mtx_); // 等待缓冲区非空处理虚假唤醒 not_empty_.wait(lock, [this] { return !queue_.empty(); }); auto val std::move(queue_.front()); // 使用移动语义避免拷贝 queue_.pop(); not_full_.notify_one(); // 通知可能等待的入队操作 return val; } // 获取当前元素数量线程安全 size_t size() const { std::lock_guardstd::mutex lock(mtx_); return queue_.size(); } private: std::queueT queue_; mutable std::mutex mtx_; // mutable允许在const成员函数中加锁 std::condition_variable not_full_; // 缓冲区不满条件变量 std::condition_variable not_empty_; // 缓冲区非空条件变量 const size_t capacity_; // 缓冲区最大容量 }; int main() { const size_t buffer_capacity 5; const int data_count 10; // 每个生产者生产10个数据 BoundedBufferint buffer(buffer_capacity); // 生产者线程向缓冲区推送数据 auto producer [buffer, data_count](int producer_id) { for (int i 0; i data_count; i) { buffer.push(producer_id * 100 i); // 生产数据ID序号 std::this_thread::sleep_for(std::chrono::milliseconds(50)); // 模拟生产耗时 } }; // 消费者线程从缓冲区取出数据 auto consumer [buffer, data_count](int consumer_id) { for (int i 0; i data_count; i) { int val buffer.pop(); // 消费数据阻塞直到有数据 std::cout Consumer consumer_id popped: val std::endl; } }; // 创建线程第二个参数为线程id std::vectorstd::thread threads; threads.emplace_back(producer, 1); // 生产者1 threads.emplace_back(producer, 2); // 生产者2 threads.emplace_back(consumer, 1); // 消费者1 threads.emplace_back(consumer, 2); // 消费者2 // 主线程等待所有线程完成join for (auto t : threads) { if (t.joinable()) { // 避免重复join如线程已detach t.join(); // 阻塞主线程直到所有生产者/消费者完成 } } std::cout All threads completed. std::endl; return 0; }进程间通信方式1.管道内核维护的一段环形内存缓冲区半双工通信。需要两个管道实现双全工。2.消息队列内核维护的 “消息链表”进程可按 “类型” 发送 / 接收消息消息包含类型和数据。单个队列即可实现双全工通信内核会为不同类型的消息维护隐性索引内核会精准定位并提取目标消息非目标类型数据仍留在队列中。3.共享内存内核分配的一块物理内存多个进程将其映射到自己的虚拟地址空间直接读写。mmap 实现共享内存通过mmap将同一文件映射到不同进程的虚拟内存中并且每个进程以可读写权限PROT_READ | PROT_WRITE和共享模式MAP_SHARED映射时完全可以实现进程间通信。#include sys/mman.h void* mmap(void* addr, size_t length, int prot, int flags, int fd, off_t offset); int munmap(void* addr, size_t length);4.信号 操作系统向进程发送的 “事件通知”如异常、中断。信号不是 “数据传输工具”而是 “事件触发器”仅传递 “发生了某类事件” 的通知最多附带少量附加数据。5.嵌套字通过网络协议如 TCP/UDP或本地协议如 AF_UNIX实现进程通信的接口。进程安全1. 互斥量Mutex需要一个全局唯一的互斥量名称适合单机内、高频、短耗时的进程间临界区同步pthread_mutex_t进程锁初始化pthread_mutexattr_t attr; pthread_mutexattr_init(attr); pthread_mutexattr_setpshared( attr, PTHREAD_PROCESS_SHARED ); pthread_mutex_init(shared-mutex, attr); pthread_mutexattr_destroy(attr);2. 文件锁跨进程的文件级同步适合与文件强相关或跨机器的低频次同步3. 信号量进程间并发控制读写锁读写锁Read-Write Lock的核心是“读写分离、允许多读单写”核心特性读锁共享锁多个线程可同时获取互不阻塞读 - 读兼容写锁排他锁仅一个线程可获取会阻塞所有读锁和其他写锁读 - 写、写 - 写互斥适用场景读多写少如缓存查询、配置读取、日志统计比普通互斥锁Mutex更高效避免读操作互相阻塞。包括了读锁的线程数、写锁占用、写锁等待数普通std::mutex是 “无脑独占”不管读写先抢锁再说而读写锁是 “先看标记再决定怎么锁”用标记区分 “读共享” 和 “写排他”标记是 “规则”锁是 “执行规则的工具”。读线程的 notify 是 “精准唤醒写线程”—— 一个就够用notify_one()写线程的 notify 是 “广播唤醒所有等待线程”—— 既要让读线程并发又要让写线程有机会用notify_all()。class ReadWriteLock { private: std::mutex mutex_; // 保护内部状态的互斥锁门卫 std::condition_variable cond_; // 阻塞/唤醒线程的条件变量传令官 int read_count_ 0; // 当前持有读锁的线程数 bool write_flag_ false; // 写锁是否被占用true占用 int write_wait_count_ 0; // 等待写锁的线程数保证写优先级 public: // -------------------------- 读锁操作 -------------------------- void lock_read() { // 1. 锁定 mutex保护状态不被并发修改 std::unique_lockstd::mutex lock(mutex_); // 2. 循环等待没有写锁占用 没有写线程等待写优先级 while (write_flag_ || write_wait_count_ 0) { cond_.wait(lock); // 释放 mutex阻塞当前线程唤醒后重新获取 mutex } // 3. 读计数1标记当前线程持有读锁 read_count_; // 4. unique_lock 析构时自动解锁 mutex无需手动 unlock } void unlock_read() { std::unique_lockstd::mutex lock(mutex_); // 1. 读计数-1当前线程释放读锁 read_count_--; // 2. 若为最后一个读线程唤醒一个等待的写线程写线程需要读计数0 if (read_count_ 0) { cond_.notify_one(); // 只唤醒一个写线程避免多个写线程竞争 } } // -------------------------- 写锁操作 -------------------------- void lock_write() { std::unique_lockstd::mutex lock(mutex_); // 1. 写等待数1标记有写线程在等让后续读线程阻塞 write_wait_count_; // 2. 循环等待没有读线程占用 没有其他写锁占用 while (read_count_ 0 || write_flag_) { cond_.wait(lock); // 释放 mutex阻塞当前线程 } // 3. 标记写锁被当前线程占用 write_flag_ true; // 4. 写等待数-1当前线程已获取锁移除等待标记 write_wait_count_--; } void unlock_write() { std::unique_lockstd::mutex lock(mutex_); // 1. 标记写锁释放 write_flag_ false; // 2. 唤醒所有等待的线程读写让它们重新竞争锁 cond_.notify_all(); } }