文章目录一、基础核心概念1.1 程序Program1.2 进程Process1.3 线程Thread1.4 串行Serial1.5 并行Parallelism1.6 并发Concurrency二、C 多线程优势2.1 提升运行效率利用多核CPU2.2 提升交互体验 响应性2.3 模块化逻辑拆分2.4 补充代价三、线程安全相关核心概念3.1 线程安全3.2 临界区Critical Section3.3 同步Synchronization3.4 互斥Mutual Exclusion3.5 线程优先级3.6 死锁Deadlock四、线程安全问题常见原因 排查方法4.1 常见原因4.2 排查方法五、C 多线程基础代码示例极简一、基础核心概念1.1 程序Program存放在磁盘上的可执行静态文件.exe/可执行二进制文件、源码编译产物是指令和数据的集合本身不运行、不占用运行时内存。示例a.exe、编译好的 C 可执行文件。1.2 进程Process程序运行后的实例操作系统资源分配的基本单位拥有独立地址空间、堆、全局变量、文件句柄、CPU时间片等系统资源。进程之间相互隔离、默认不能直接访问彼此内存进程切换开销很大。一个进程至少包含1 个主线程。1.3 线程Thread进程内部的执行分支/执行流是操作系统CPU调度的基本单位共享所属进程的地址空间、全局变量、堆、文件资源拥有自己独立栈、寄存器、局部变量。线程切换开销远小于进程切换轻量级执行单元。C11 引入标准库std::thread1.4 串行Serial依次执行任务同一时刻只有一个任务在执行完成一个再做下一个优点逻辑简单、天然线程安全、无竞争缺点CPU多核资源浪费、整体耗时长1.5 并行Parallelism同一时刻多个任务同时运行依赖多核CPU/多处理器硬件真正同时执行多条指令物理上同步运行用于把大任务分块同时计算提升计算速度。例多核CPU上多个线程各自跑在不同CPU核心1.6 并发Concurrency宏观上同时推进多个任务微观上交替执行单核CPU靠操作系统时间片轮转调度快速切换线程给人同时运行的错觉并发 ≠ 并行并行属于并发的子集核心目的提升响应性、避免主线程阻塞如UI程序二、C 多线程优势2.1 提升运行效率利用多核CPU把计算密集型任务拆分为多个子任务并行执行充分利用多核处理器算力缩短总运算时间分离IO等待文件读写、网络请求和计算任务IO阻塞时CPU线程可继续运算避免CPU空等提高整体吞吐量。2.2 提升交互体验 响应性主线程UI/交互线程负责响应用户输入、刷新界面子线程执行耗时运算/IO任务防止主线程卡死、程序无响应保证交互流畅。2.3 模块化逻辑拆分按功能职责拆分线程通信线程、计算线程、日志线程简化业务逻辑架构部分场景可提高整体系统稳定性例如隔离后台任务异常减少主线程崩溃概率。2.4 补充代价额外开销线程创建/销毁、上下文切换、同步锁等待增加代码复杂度引入线程竞争、死锁等风险。三、线程安全相关核心概念3.1 线程安全定义多线程同时访问共享数据全局变量、静态变量、堆内存对象时程序行为结果始终正确、符合预期不会出现数据错乱、脏读、异常崩溃。非线程安全根源多个线程同时读写同一可变共享资源竞争资源 / 临界区读写操作不是原子操作。✅ 只读常量/const对象天然线程安全❌ 可读写全局/静态变量、共享对象默认非线程安全3.2 临界区Critical Section访问共享可变资源的代码片段同一时刻只允许一个线程进入执行防止并发读写冲突。3.3 同步Synchronization协调多个线程执行顺序、访问时机保证共享资源访问有序、数据一致C常用同步机制std::mutex互斥锁std::lock_guard/std::unique_lockRAII锁管理std::condition_variable条件变量线程等待/唤醒生产者消费者模型std::atomicT原子类型无锁线程安全适用于简单数值变量std::future/std::promise/std::async异步任务同步3.4 互斥Mutual Exclusion同步核心机制保证同一时刻仅有一个线程进入临界区访问共享资源实现互斥访问。std::mutex基础互斥锁std::recursive_mutex递归互斥锁同一线程可多次加锁std::timed_mutex超时互斥锁避免永久阻塞3.5 线程优先级OS 调度参数决定CPU分配时间片的优先程度优先级高的线程更容易被CPU调度执行风险优先级反转低优先级线程持有锁高优先级线程持续等待、饥饿问题C标准本身不直接提供跨平台优先级API通常依赖平台APIWindows/Linux系统调用。3.6 死锁Deadlock✅ 死锁定义多个线程互相持有对方需要的锁/资源互相无限等待所有线程都无法继续执行程序卡死。✅ 死锁四大必要条件Coffman条件互斥条件资源是独占访问锁互斥持有并等待线程持有已有锁同时请求另外一把锁不释放已有锁不可剥夺条件锁不能被操作系统强行回收循环等待条件线程形成环形等待依赖A等B锁B等C锁C等A锁✅ 死锁解决/预防方法固定锁的加锁顺序所有线程按相同顺序申请锁使用统一加锁函数std::lock(多个mutex)一次性加锁设置超时锁std::timed_mutex避免无限阻塞减少嵌套锁减少全局共享锁缩小临界区代码范围使用原子变量减少锁依赖四、线程安全问题常见原因 排查方法4.1 常见原因共享可变数据竞争数据竞争 Data Race多线程无保护并发读写同一个非原子共享变量/对象指令被CPU乱序执行、多核缓存不一致造成脏数据、数值错乱、崩溃例如全局计数变量int cnt多线程直接cnt非原子操作会拆分为读/改/写三步错误/不当使用锁漏加锁、加锁粒度不合理锁太大性能差 / 锁太小没覆盖全部临界区、忘记解锁、重复加锁锁嵌套不当形成循环依赖引发死锁混用同步机制时序错乱内存可见性 CPU乱序优化问题编译器/CPU指令重排、CPU缓存缓存行不一致一个线程修改的数据对其他线程不可见解决std::atomic、内存屏障、volatile慎用C不保证线程安全仅影响编译器优化对象生命周期问题野指针/悬垂引用主线程提前析构共享对象子线程继续访问已销毁对象造成内存越界崩溃异步回调、lambda捕获引用错误捕获局部变量引用变量提前销毁虚假唤醒Spurious Wakeupstd::condition_variable条件变量被意外唤醒未检查真实条件直接执行代码引发错误正确写法while(条件不满足) cv.wait(...)而非if优先级调度问题、线程饥饿、异步回调时序错误4.2 排查方法代码审查检查全局/静态变量、共享对象访问逻辑确认临界区范围和锁匹配检查lambda捕获方式值捕获vs引用捕获检查锁嵌套顺序排查循环等待风险工具检测内存/并发检查工具Valgrind、AddressSanitizer(ASAN)、ThreadSanitizer(TSan)检测data race数据竞争调试器gdb/lldb/VS调试断点查看线程状态、锁持有情况、调用栈定位死锁/异常线程日志追踪记录线程ID、锁获取/释放时机、变量值变化复现与简化测试增加并发压力测试复现偶发bug逐步缩小临界区代码定位冲突变量使用原子类型/无锁结构做对照验证加锁/不加锁对照复现问题增加断言/校验对共享数据增加状态校验、边界检查提前捕获脏读/非法访问检查条件变量等待逻辑用while循环防虚假唤醒查看CPU/系统状态死锁时CPU占用率很低线程卡在等待锁状态可查看线程阻塞调用栈五、C 多线程基础代码示例极简#includeiostream#includethread#includemutexstd::mutex mtx;intcount0;voidadd(){for(inti0;i10000;i){std::lock_guardstd::mutexlock(mtx);// RAII自动加锁/解锁count;}}intmain(){std::threadt1(add);std::threadt2(add);t1.join();t2.join();std::coutcountstd::endl;return0;}
《编程通识》之多线程
发布时间:2026/7/15 23:32:08
文章目录一、基础核心概念1.1 程序Program1.2 进程Process1.3 线程Thread1.4 串行Serial1.5 并行Parallelism1.6 并发Concurrency二、C 多线程优势2.1 提升运行效率利用多核CPU2.2 提升交互体验 响应性2.3 模块化逻辑拆分2.4 补充代价三、线程安全相关核心概念3.1 线程安全3.2 临界区Critical Section3.3 同步Synchronization3.4 互斥Mutual Exclusion3.5 线程优先级3.6 死锁Deadlock四、线程安全问题常见原因 排查方法4.1 常见原因4.2 排查方法五、C 多线程基础代码示例极简一、基础核心概念1.1 程序Program存放在磁盘上的可执行静态文件.exe/可执行二进制文件、源码编译产物是指令和数据的集合本身不运行、不占用运行时内存。示例a.exe、编译好的 C 可执行文件。1.2 进程Process程序运行后的实例操作系统资源分配的基本单位拥有独立地址空间、堆、全局变量、文件句柄、CPU时间片等系统资源。进程之间相互隔离、默认不能直接访问彼此内存进程切换开销很大。一个进程至少包含1 个主线程。1.3 线程Thread进程内部的执行分支/执行流是操作系统CPU调度的基本单位共享所属进程的地址空间、全局变量、堆、文件资源拥有自己独立栈、寄存器、局部变量。线程切换开销远小于进程切换轻量级执行单元。C11 引入标准库std::thread1.4 串行Serial依次执行任务同一时刻只有一个任务在执行完成一个再做下一个优点逻辑简单、天然线程安全、无竞争缺点CPU多核资源浪费、整体耗时长1.5 并行Parallelism同一时刻多个任务同时运行依赖多核CPU/多处理器硬件真正同时执行多条指令物理上同步运行用于把大任务分块同时计算提升计算速度。例多核CPU上多个线程各自跑在不同CPU核心1.6 并发Concurrency宏观上同时推进多个任务微观上交替执行单核CPU靠操作系统时间片轮转调度快速切换线程给人同时运行的错觉并发 ≠ 并行并行属于并发的子集核心目的提升响应性、避免主线程阻塞如UI程序二、C 多线程优势2.1 提升运行效率利用多核CPU把计算密集型任务拆分为多个子任务并行执行充分利用多核处理器算力缩短总运算时间分离IO等待文件读写、网络请求和计算任务IO阻塞时CPU线程可继续运算避免CPU空等提高整体吞吐量。2.2 提升交互体验 响应性主线程UI/交互线程负责响应用户输入、刷新界面子线程执行耗时运算/IO任务防止主线程卡死、程序无响应保证交互流畅。2.3 模块化逻辑拆分按功能职责拆分线程通信线程、计算线程、日志线程简化业务逻辑架构部分场景可提高整体系统稳定性例如隔离后台任务异常减少主线程崩溃概率。2.4 补充代价额外开销线程创建/销毁、上下文切换、同步锁等待增加代码复杂度引入线程竞争、死锁等风险。三、线程安全相关核心概念3.1 线程安全定义多线程同时访问共享数据全局变量、静态变量、堆内存对象时程序行为结果始终正确、符合预期不会出现数据错乱、脏读、异常崩溃。非线程安全根源多个线程同时读写同一可变共享资源竞争资源 / 临界区读写操作不是原子操作。✅ 只读常量/const对象天然线程安全❌ 可读写全局/静态变量、共享对象默认非线程安全3.2 临界区Critical Section访问共享可变资源的代码片段同一时刻只允许一个线程进入执行防止并发读写冲突。3.3 同步Synchronization协调多个线程执行顺序、访问时机保证共享资源访问有序、数据一致C常用同步机制std::mutex互斥锁std::lock_guard/std::unique_lockRAII锁管理std::condition_variable条件变量线程等待/唤醒生产者消费者模型std::atomicT原子类型无锁线程安全适用于简单数值变量std::future/std::promise/std::async异步任务同步3.4 互斥Mutual Exclusion同步核心机制保证同一时刻仅有一个线程进入临界区访问共享资源实现互斥访问。std::mutex基础互斥锁std::recursive_mutex递归互斥锁同一线程可多次加锁std::timed_mutex超时互斥锁避免永久阻塞3.5 线程优先级OS 调度参数决定CPU分配时间片的优先程度优先级高的线程更容易被CPU调度执行风险优先级反转低优先级线程持有锁高优先级线程持续等待、饥饿问题C标准本身不直接提供跨平台优先级API通常依赖平台APIWindows/Linux系统调用。3.6 死锁Deadlock✅ 死锁定义多个线程互相持有对方需要的锁/资源互相无限等待所有线程都无法继续执行程序卡死。✅ 死锁四大必要条件Coffman条件互斥条件资源是独占访问锁互斥持有并等待线程持有已有锁同时请求另外一把锁不释放已有锁不可剥夺条件锁不能被操作系统强行回收循环等待条件线程形成环形等待依赖A等B锁B等C锁C等A锁✅ 死锁解决/预防方法固定锁的加锁顺序所有线程按相同顺序申请锁使用统一加锁函数std::lock(多个mutex)一次性加锁设置超时锁std::timed_mutex避免无限阻塞减少嵌套锁减少全局共享锁缩小临界区代码范围使用原子变量减少锁依赖四、线程安全问题常见原因 排查方法4.1 常见原因共享可变数据竞争数据竞争 Data Race多线程无保护并发读写同一个非原子共享变量/对象指令被CPU乱序执行、多核缓存不一致造成脏数据、数值错乱、崩溃例如全局计数变量int cnt多线程直接cnt非原子操作会拆分为读/改/写三步错误/不当使用锁漏加锁、加锁粒度不合理锁太大性能差 / 锁太小没覆盖全部临界区、忘记解锁、重复加锁锁嵌套不当形成循环依赖引发死锁混用同步机制时序错乱内存可见性 CPU乱序优化问题编译器/CPU指令重排、CPU缓存缓存行不一致一个线程修改的数据对其他线程不可见解决std::atomic、内存屏障、volatile慎用C不保证线程安全仅影响编译器优化对象生命周期问题野指针/悬垂引用主线程提前析构共享对象子线程继续访问已销毁对象造成内存越界崩溃异步回调、lambda捕获引用错误捕获局部变量引用变量提前销毁虚假唤醒Spurious Wakeupstd::condition_variable条件变量被意外唤醒未检查真实条件直接执行代码引发错误正确写法while(条件不满足) cv.wait(...)而非if优先级调度问题、线程饥饿、异步回调时序错误4.2 排查方法代码审查检查全局/静态变量、共享对象访问逻辑确认临界区范围和锁匹配检查lambda捕获方式值捕获vs引用捕获检查锁嵌套顺序排查循环等待风险工具检测内存/并发检查工具Valgrind、AddressSanitizer(ASAN)、ThreadSanitizer(TSan)检测data race数据竞争调试器gdb/lldb/VS调试断点查看线程状态、锁持有情况、调用栈定位死锁/异常线程日志追踪记录线程ID、锁获取/释放时机、变量值变化复现与简化测试增加并发压力测试复现偶发bug逐步缩小临界区代码定位冲突变量使用原子类型/无锁结构做对照验证加锁/不加锁对照复现问题增加断言/校验对共享数据增加状态校验、边界检查提前捕获脏读/非法访问检查条件变量等待逻辑用while循环防虚假唤醒查看CPU/系统状态死锁时CPU占用率很低线程卡在等待锁状态可查看线程阻塞调用栈五、C 多线程基础代码示例极简#includeiostream#includethread#includemutexstd::mutex mtx;intcount0;voidadd(){for(inti0;i10000;i){std::lock_guardstd::mutexlock(mtx);// RAII自动加锁/解锁count;}}intmain(){std::threadt1(add);std::threadt2(add);t1.join();t2.join();std::coutcountstd::endl;return0;}