Mandelbrot 教学示例增强:主线程渲染循环至 ≥1.5s,让界面冻结肉眼可感

实测单帧 820×640@256 单线程仅约 70ms,远低于人眼可感阈值,直接算一帧看不出
「冻结」。故「主线程渲染」按钮改为自适应循环渲染直到累计 ≥ 1500ms(实测 ~23 帧 /
1506ms),上限 100 轮防止极慢机器卡死过久。冻结的本质是事件循环被占用,循环只是
模拟任意一个占着 GUI 线程不返回的耗时操作。

- mandelbrot_widget.cpp:onRenderOnMainThread 改 do-while 循环,标签加「· N 轮」
- README.md:补冻结时长说明与课堂演示技巧
- 设计文档同步:三段式对比章节更新实测数据与冻结本质解释
- 新增 docs/teaching/MULTITHREADING.md:多线程教学要点汇总

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
张宗平
2026-07-09 16:50:46 +08:00
parent 12950da129
commit 993aa30a1c
4 changed files with 391 additions and 9 deletions
@@ -143,15 +143,23 @@ void MandelbrotWidget::onRenderOnMainThread() {
pixmap_ = QImage(vp_.width, vp_.height, QImage::Format_RGB32);
const int H = vp_.height;
const int chunk = 24; // 分块调 renderStrip 只为复用同一纯函数
for (int y = 0; y < H; y += chunk) {
const QImage s = renderStrip(vp_, pal_, y, qMin(y + chunk, H));
QPainter p(&pixmap_);
p.drawImage(0, y, s);
}
// 自适应加重:单帧仅约几十毫秒(实测 820x640@256 ≈ 66ms),不足以让「冻结」肉眼可感。
// 故循环渲染直到累计 ≥ 1500ms——冻结的本质是事件循环被占用,与「算什么」无关,
// 这模拟任意一个「占着 GUI 线程不返回事件循环」的耗时操作(解析大文件、复杂运算……)。
// 上限 100 轮防止极慢机器卡死过久。
int frames = 0;
do {
for (int y = 0; y < H; y += chunk) {
const QImage s = renderStrip(vp_, pal_, y, qMin(y + chunk, H));
QPainter p(&pixmap_);
p.drawImage(0, y, s);
}
++frames;
} while (timer_.elapsed() < 1500 && frames < 100);
// 注意:本函数返回前事件循环不跑——期间按钮无响应、窗口拖不动、paintEvent 排队等待。
update();
label_->setText(QString(QStringLiteral("主线程渲染 (界面冻结) · %1 ms"))
.arg(int(timer_.elapsed())));
label_->setText(QString(QStringLiteral("主线程渲染 (界面冻结) · %1 ms · %2 轮"))
.arg(int(timer_.elapsed())).arg(frames));
}
// ---- 单/多线程切换:复用同一套分块逻辑,仅切线程池大小 ----