线程启动与join
约 60 分钟
线程启动与join
今天先解决一个真实问题
我们不从语法表开始。这一课要让一段程序在你的机器上明确地工作,并让你能解释它为什么工作。当前程序走到“并发程序设计入门”中的“线程启动与join”。先完成任务:写一个能编译运行的最小程序展示“线程启动与join”,记录输入、关键对象状态、输出和编译命令,并用规则 join establishes completion 解释结果。运行结果只是入口;真正要掌握的是对象何时产生、谁能修改、什么时候失效,以及编译器或运行库在错误路径上给了什么证据。
(本段定位:cs-40-cpp-programming/11/01/线程启动与join。)
本课核心规则是 join establishes completion。把这句话翻成自己的话,再指出程序中的具体行、对象和事件。示例采用 8 个输入元素、每个逻辑对象约 6 字节的简化场景;数字可以换,规则不能靠记住某次输出维持。凡是涉及实现定义或平台差异,都要打印或声明编译器版本、语言标准和关键类型宽度。
(本段定位:cs-40-cpp-programming/11/01/线程启动与join。)
先建立编译和执行模型
C++程序不是把文本直接“跑起来”。源文件经过预处理形成翻译单元,编译器做语法和类型检查并产生目标代码,链接器解析跨文件符号,装载后才进入main。分析“线程启动与join”时先判断现象发生在编译期、链接期还是运行期。类型不匹配、未定义符号、悬空引用和异常退出属于不同阶段,修复方法也不同。
(本段定位:cs-40-cpp-programming/11/01/线程启动与join。)
请保留一条在线兼容的最小命令,例如 c++ -std=c++17 -Wall -Wextra -pedantic main.cpp && ./a.out。先让警告为零,再运行正常、边界、失败和回归输入。若专门比较C++20扩展,应另建实验并明确标准,不能让在线题暗中依赖更高版本。若错误信息很多,先找最早出现、位置最接近自己代码的诊断;后续错误常是第一个问题造成的级联。
(本段定位:cs-40-cpp-programming/11/01/线程启动与join。)
跟我逐行推演
先写输入和前置条件,然后逐行记录本节关键对象的类型、值、所有者、作用域与剩余生存期。遇到函数调用就写参数怎样绑定,遇到容器操作就写大小、容量和迭代器有效性,遇到资源就写取得与释放,遇到线程就写同步关系。每一步都写成 旧状态 --语句/事件--> 新状态。
(本段定位:cs-40-cpp-programming/11/01/线程启动与join。)
现在按 join establishes completion 预测输出,再编译运行。预测与实际不同,不要立即改代码碰运气;先寻找第一个偏差:是读取了错误输入、发生了隐式转换、违反前置条件、对象已经失效,还是算法成本模型与工作负载不同?只改一个原因并重跑原测试,才能证明修复有效。
(本段定位:cs-40-cpp-programming/11/01/线程启动与join。)
这个设计为什么存在
C++同时追求零开销抽象、确定资源管理、泛型复用和与底层系统互操作,因此许多规则是在表达能力、性能和安全之间做选择。“线程启动与join”不能只回答“怎么写”,还要回答它保护了什么不变量、允许编译器做什么优化,以及程序员承担了什么责任。
做一次反事实:如果删掉本节规则,哪种错误会被接受?如果换成另一种写法,类型安全、所有权清晰度、异常安全、局部性或渐进复杂度怎样变化?给出至少一个具体输入,不用“更优雅”“更快”这类无法检验的词替代证据。
边界和失败路径
严格边界是:不得把“线程启动与join”只写成语法模板;必须说明类型、所有权/生存期、失败输入、复杂度或并发可见性中与本节有关的边界。把它落实成测试:空输入、零、最大值、重复元素、无效索引、空指针、抛异常、资源取得一半失败、迭代器失效、多个线程交错中选择两项。写出错误实现的可观察表现,以及正确程序应拒绝、恢复或保持的状态。
(本段定位:cs-40-cpp-programming/11/01/线程启动与join。)
未定义行为尤其不能靠“我这里输出正常”证明正确。编译器没有义务稳定复现越界、悬空、数据竞态或有符号溢出的结果。应从语言规则排除错误,并用警告、sanitizer和测试作为辅助证据。工具没报错不等于程序没有未定义行为。
写出可维护的版本
先用直白实现证明语义,再做抽象。函数名表达动作,类型表达约束,资源由对象管理,接口只暴露必要状态。若引入模板或重载,错误信息和使用方式仍应清楚;若选择STL容器,必须从主要操作、迭代器稳定性和局部性解释,而不是因为“大家都用vector”。
优化前固定工作负载、编译选项、指标和基线。比较算法时先看复杂度,再看常数、分配、缓存和输入规模。本节产物将作为《数据竞态与互斥锁》的输入或约束。下一课会复用本课的程序或契约,所以本课测试必须保留,形成持续回归而不是一次性作业。
可运行练习
每章末两道代码题都由后端以C++编译运行,并在离线发布前真实执行至少四组测试。提交必须从标准输入读取、写到标准输出,不依赖本地文件、随机时间或第三方库。题面若要求算法,伪代码和硬编码样例不得分;编译失败、运行崩溃、超时或输出格式错误都视为未通过。
(本段定位:cs-40-cpp-programming/11/01/线程启动与join。)
本课另外绑定单选、多选、计算和论述。计算题必须写中间量和复杂度适用条件;论述按编译阶段、对象状态、规则、失败反例与验证评分。只抄语法、只贴代码或只说“编译器会处理”不能证明理解。
下课前闭卷复述
请离开代码回答六问:一,“线程启动与join”解决什么问题;二,规则 join establishes completion 约束了谁;三,指出编译期、链接期或运行期;四,画出三个状态变化;五,构造一个失败输入;六,说明本节怎样连接下一课。然后从空文件重写最小示例并通过测试。
(本段定位:cs-40-cpp-programming/11/01/线程启动与join。)
如果你能把程序讲给另一个人,让他预测修改一行后的诊断或输出,并能用对象状态而不是术语解释,就算学会。否则回到最早的编译命令和第一处状态偏差,一步一步重新推。
Practice
本课练习
先独立作答再提交;编程题会在隔离沙箱中真实编译、运行并对拍。
在“线程启动与join”的简化模型中,6个线程各完成2次受互斥保护的加一,初值0,最终计数是多少?只填数值,并在草稿保留式子。
知识锚点为线程启动与join。完成任务“写一个能编译运行的最小程序展示“线程启动与join”,记录输入、关键对象状态、输出和编译命令,并用规则 join establishes completion 解释结果”:说明编译/链接/运行阶段,列关键对象的类型、所有权或生存期,按join establishes completion推演,并为边界“不得把“线程启动与join”只写成语法模板;必须说明类型、所有权/生存期、失败输入、复杂度或并发可见性中与本节有关的边界”给出失败测试和修复。
【10分量表】阶段与契约2分;对象状态3分;规则推演2分;失败测试与修复3分。只贴代码不得分。
严格验收“线程启动与join”时应提交哪些证据?
独立场景标识:u11-6-并发程序设计入门-线程启动与join;不得引用其他试卷答案。
多选题:必须选全正确项,漏选或多选均不得分。