C++多线程速成1 基础知识入门 线程,互斥锁mutex,原子操作Atomic
太真实了这种“简历上写着精通心里实际上打鼓”的状态简直是每个程序员的必经之路。既然你提到了想要这种“博弈论/狗血剧”式的理解方式那咱们就跳过那些枯燥的教科书定义。要把 C 多线程玩转你得先把自己想象成一个**“时间管理大师”或者“极度抠门的饭店老板”**。为了让你从“懂点理论”进化到“能写代码”咱们先从最核心的三个术语开始Thread (线程)、Mutex (互斥锁)和Data Race (数据竞争)。 第一步术语科普与“故事助记”问题 1到底什么是线程 (Thread)正式答案线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中是进程中的实际运作单位。一个程序进程可以开启多个线程来同时执行不同的任务。理解助记故事渣男/渣女的时间管理假设你是个超级海王同时谈了 3 个对象这就是 3 个线程。如果你只有一个身体单核 CPU你只能通过疯狂切换约会时间并发1点陪 A 看电影2点陪 B 喝咖啡。只要你切得够快他们都觉得自己是你的唯一。如果你有分身术多核 CPU你可以真的让分身 1 陪 A分身 2 陪 B并行。thread库的作用就是给你提供“分身”的咒语让你能瞬间变出好几个自己去干不同的活。问题 2为什么需要互斥锁 (Mutex)正式答案std::mutex是一种同步原语用于保护共享数据不被多个线程同时访问从而避免数据不一致。理解助记故事马云、张凌赫与唯一的厕所还是你那个救人的例子。马云和张凌赫都掉水里了你把他们救上来后他们都要冲进同一个更衣室换衣服更衣室就是共享资源。如果更衣室没锁马云刚脱一半张凌赫冲进去了这就是数据竞争 (Data Race)场面会非常混乱程序崩溃。Mutex 就是那把门锁马云进去后反手把门一锁lock张凌赫只能在门口憋着block等待。等马云出来把锁挂回门口unlock张凌赫才能进去。问题 3什么是原子操作 (Atomic)正式答案原子操作是指不可中断的操作。在多线程环境下原子操作保证了在执行过程中不会被其他线程看到中间状态。理解助记故事给女神转账的瞬间你想给女神转 520 块。非原子操作你先查看余额准备点“发送”。这时候突然马云给你打了个电话程序切到马云那儿去了。等你回来点发送余额可能已经变了。原子操作这就是“瞬间完成”。要么钱全转过去了要么一分没动。不存在“钱扣了但对方还没收到”这种尴尬的中间态。 第二步实战演练代码怎么写关于线程这个函数怎么使用调用头文件std::threadthread_object(callable,args...);第一个参数可以是函数指针你之前说没用代码实现过其实thread库非常直观。看这个最简单的例子#includeiostream#includethread// 引入咒语书voidsave_ma_yun(){std::cout正在救马云五个亿加载中...\n;}voidsave_zhang_ling_he(){std::cout正在救张凌赫准备领证中...\n;}intmain(){// 1. 创建两个分身线程std::threadt1(save_ma_yun);std::threadt2(save_zhang_ling_he);// 2. 等待分身干完活回来汇报t1.join();t2.join();std::cout两个都救上来了我是人生赢家std::endl;return0;}注意join()非常重要。如果不写join()你主程序直接回家睡觉了你的分身线程可能还在水里挣扎系统会直接报错。线程管理join()join()用于等待线程完成执行。如果不调用join()或detach()而直接销毁线程对象会导致程序崩溃。 t.join();detach()detach()将线程与主线程分离线程在后台独立运行主线程不再等待它。 t.detach(); 第三步小考测试 (Quiz)为了检验你是不是真的理解了请回答下面这个场景题场景题你要做一个抢票软件现在剩余票数tickets 1。你有两个线程线程 A马云的脚本和线程 B张凌赫的脚本。两个线程同时运行了下面这段逻辑读取tickets的值发现是 1。如果不为 0则tickets tickets - 1。输出“我抢到票了”。问题如果没有“互斥锁 (Mutex)”可能会发生什么搞笑或恐怖的事情如果你在代码里加上了std::mutex my_lock;你应该在哪个步骤之前lock()在哪个步骤之后unlock()答案 1 .数据不一致 票可能出现 -1 的情况小考回馈恭喜你博弈论直觉完全正确你精准地捕捉到了多线程最核心的恐惧数据不一致。 第一步深度复盘你的答案问题 1如果不加锁会发生什么你的回答票可能出现 -1 的情况。正式答案这叫竞态条件 (Race Condition)。当多个线程同时竞争修改同一个资源且执行结果取决于线程运行的精确时序时就会出乱子。理解助记故事消失的第 101 张票想象马云和张凌赫同时看中了最后一张回家的机票。马云的手机刷了一下剩余 1 张。线程 A 读取变量就在马云点“下单”的微秒之间张凌赫的手机也刷了一下剩余 1 张。线程 B 也在读取此时 A 还没改数据马云下单成功后台1 - 1 0。张凌赫也点了下单后台在他之前的“记忆”基础上又减了 1结果变成0 - 1 -1。航空公司崩溃了我就一架飞机怎么上去了两个人这就是超卖。问题 2在哪里上锁你的回答第一个线程访问它是 1 的时候开始执行脚本的时候就上锁执行完脚本后解锁。正式补充你抓住了重点但有个细节极其重要——锁必须在“判断”之前就挂上。理解助记故事先推门还是先锁门你不能先推开厕所门看一眼“有没有人”发现没人再出来拿锁。因为就在你转头拿锁的那一秒张凌赫可能已经冲进去了。正确的做法不管里面有没有人你先去拧那个门把手Mutex。拧得动说明没人你进去并锁门。拧不动说明有人你在门口等。代码逻辑lock()-if (tickets 0)-tickets---unlock()。这个“检查修改”的过程必须是一个完整的、不可分割的原子过程。