Skip to content
Gains Summary
Main Navigation 首页 / Home
C++ 编程 / C++ Programming
系统与高性能 / Systems & Performance
Web 开发 / Web Development
人工智能 / Artificial Intelligence
工业软件 / Industrial Software
其他内容 / Other Topics
C++ 编程 / C++系统与性能 / SystemsWeb 开发 / Web人工智能 / AI工业软件 / Industrial

外观

Sidebar Navigation

← C++ 编程 / C++ Programming

Qt 与可视化 / Qt & Visualization

1. Qt 6 与科学可视化路线 / Qt 6 and Scientific Visualization Roadmap

2. 信号槽机制深度解析 / Qt Signals and Slots in Depth

3. 对象树与内存管理 / QObject Trees and Memory Management

4. CAE开发常用核心API实战 / Essential Qt APIs for CAE Development

5. Model/View架构与CAE数据处理 / Model-View Architecture and CAE Data Processing

6. 多线程与求解器集成 / Multithreading and Solver Integration

7. CAE软件架构实战模式 / Practical Architecture Patterns for CAE Software

8. 3D可视化与交互 / Interactive 3D Visualization

9. 实战案例:从零拆解一个FEA前后处理器 / Building an FEA Pre- and Post-Processor from Scratch

本页目录

多线程与求解器集成 / Multithreading and Solver Integration ​

📅 创建时间:2026-07-13 🏷️ 标签:#Qt #QThread #QtConcurrent #并行计算 #求解器 #CAE 📚 前置知识:Signal Slot Deep Dive、Core Api Essentials


📋 本章目标 ​

  • 理解QThread的正确用法——Worker-Object模式
  • 掌握moveToThread()的工作原理和线程亲和性
  • 掌握QtConcurrent的map/reduce/run并行工具
  • 理解跨线程信号槽的QueuedConnection机制
  • 掌握GUI线程的黄金法则:绝对不在Worker线程操作UI
  • 掌握求解器后台运行+进度上报的完整模式
  • 掌握QMutex/QReadWriteLock/QSemaphore的CAE应用
  • 能够设计CAE软件的多线程架构

专题扩展 ​

  • Qt 线程取消与关闭协议

第1部分:QThread正确用法 ​

1.1 两种合法模型及其适用场景 ​

cpp
// ===== 模型A:继承QThread重写run(),适合自包含阻塞任务/自定义循环 =====
class SolverThread : public QThread {
    Q_OBJECT
    void run() override {
        // 这里的代码在子线程执行
        solve();  // run() 在新线程执行,可以安全发射信号
    }
};
// 边界:QThread对象本身仍活在创建它的线程;不要把面向工作线程的槽
// 直接写在QThread对象上并假定它们会在run()线程执行。

// ===== 模型B:Worker-Object + moveToThread(),适合事件驱动对象 =====
class SolverWorker : public QObject {
    Q_OBJECT
public slots:
    void doWork(const QString& inputFile) {
        // ★ 这里的代码在子线程执行!
        emit progressUpdated(0, "Starting...");

        bool ok = runSolver(inputFile);

        emit progressUpdated(100, ok ? "Done" : "Failed");
        emit finished(ok);
    }

signals:
    void progressUpdated(int percent, const QString& msg);
    void finished(bool success);
};

// 使用:
void MainWindow::startSolver() {
    QThread* thread = new QThread(this);     // 创建线程
    SolverWorker* worker = new SolverWorker(); // Worker无parent!

    worker->moveToThread(thread);            // ★ Worker移到子线程

    // 线程start时启动工作
    const QString inputFile = currentInputFile();
    connect(thread, &QThread::started, worker,
            [worker, inputFile] { worker->doWork(inputFile); });
    // Worker结束 → 线程退出
    connect(worker, &SolverWorker::finished, thread, &QThread::quit);
    // 清理
    connect(worker, &SolverWorker::finished, worker, &QObject::deleteLater);
    connect(thread, &QThread::finished, thread, &QObject::deleteLater);

    thread->start();  // ★ 触发QThread::started信号 → worker::doWork在子线程执行
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

1.2 moveToThread 的本质——线程亲和性 ​

┌─────────────────────────────────────────────────────────────┐
│              moveToThread() 与线程亲和性                      │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  QObject有"线程亲和性(Thread Affinity)":                   │
│                                                             │
│  • QObject创建时,亲和性 = 当前线程                           │
│  • moveToThread(t) 改变亲和性到目标线程                      │
│  • AutoConnection 在信号发射时比较当前执行线程与接收者亲和线程│
│    - 相同 → DirectConnection;不同 → QueuedConnection       │
│                                                             │
│  ┌──────────────────────────────────────────────────┐      │
│  │ 主线程(GUI)         │  工作线程(求解器)         │      │
│  │                       │                          │      │
│  │  MainWindow           │  SolverWorker             │      │
│  │  (主线程亲和)         │  (子线程亲和)              │      │
│  │     │                 │     │                     │      │
│  │     │ connect(        │     │                     │      │
│  │     │   worker,       │     │                     │      │
│  │     │   &SolverWorker │     │                     │      │
│  │     │   ::progress,   │     │                     │      │
│  │     │   this,         │     │                     │      │
│  │     │   &MainWindow   │     │                     │      │
│  │     │   ::updateBar); │     │                     │      │
│  │     │                 │     │                     │      │
│  │     │   → AutoConnection自动变为QueuedConnection   │      │
│  │     │   → emit progress(50)在工作线程发生          │      │
│  │     │   → updateBar(50)事件排队到主线程事件循环    │      │
│  └──────────────────────────────────────────────────┘      │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

1.3 线程生命周期管理 ​

cpp
// ===== 完整的线程生命周期 =====
class ThreadedSolver : public QObject {
    Q_OBJECT
public:
    void start(const QString& input) {
        m_thread = new QThread();
        m_worker = new SolverWorker();
        m_worker->moveToThread(m_thread);

        // start → work
        connect(m_thread, &QThread::started,
                m_worker, [worker = m_worker, input]() { worker->doWork(input); });

        // work done → clean
        connect(m_worker, &SolverWorker::finished,
                m_thread, &QThread::quit);
        connect(m_thread, &QThread::finished,
                m_worker, &QObject::deleteLater);
        connect(m_thread, &QThread::finished,
                m_thread, &QObject::deleteLater);

        m_thread->start();
    }

    void cancel() {
        if (m_worker) m_worker->cancel();
    }

private:
    QThread* m_thread = nullptr;
    SolverWorker* m_worker = nullptr;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

第2部分:QtConcurrent——高级并行 ​

2.1 QtConcurrent::map —— 并行网格操作 ​

cpp
// ===== 并行计算所有单元的质量指标 =====

// 定义计算函数
double computeElementQuality(const Element& elem,
                              const std::vector<Node>& nodes) {
    // 计算雅可比、翘曲度、内角等质量指标
    // 返回质量值 [0, 1],1=完美
    return computeJacobian(elem, nodes) * computeSkewness(elem, nodes);
}

// 用QtConcurrent::map并行处理
void computeMeshQuality(const std::vector<Element>& elems,
                         const std::vector<Node>& nodes,
                         std::vector<double>& outQuality)
{
    // 准备索引序列
    QList<int> indices;
    for (size_t i = 0; i < elems.size(); i++) indices.append(i);
    outQuality.resize(elems.size());

    // ★ 并行map:每个线程处理一部分元素
    auto future = QtConcurrent::map(indices, [&](int i) {
        outQuality[i] = computeElementQuality(elems[i], nodes);
    });

    future.waitForFinished();  // 等待全部完成
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

2.2 QtConcurrent::mappedReduced —— 全局量归约 ​

cpp
// ===== 并行计算总质量 + 找出最差单元 =====
struct QualityResult {
    double sum = 0.0;
    double min = 1.0;
    int worstElement = -1;
};

void reduceFunc(QualityResult& acc, const QualityResult& partial) {
    acc.sum += partial.sum;
    if (partial.min < acc.min) {
        acc.min = partial.min;
        acc.worstElement = partial.worstElement;
    }
}

void analyzeMesh(const std::vector<Element>& elems) {
    auto future = QtConcurrent::mappedReduced(
        elems,
        // map: 每个元素 → 局部结果
        [&](const Element& e) -> QualityResult {
            double q = computeElementQuality(e, nodes);
            return {q, q, e.id};
        },
        // reduce: 合并两个结果
        &reduceFunc
    );

    future.waitForFinished();
    QualityResult global = future.result();
    qDebug() << "Average quality:" << (global.sum / elems.size());
    qDebug() << "Worst element:" << global.worstElement
             << "quality:" << global.min;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

2.3 QFutureWatcher —— 监控并行进度 ​

cpp
// ===== 带进度条的并行计算 =====
void startParallelQualityCheck() {
    auto* watcher = new QFutureWatcher<void>(this);

    connect(watcher, &QFutureWatcher<void>::progressValueChanged,
            m_progressBar, &QProgressBar::setValue);
    connect(watcher, &QFutureWatcher<void>::finished,
            this, &MainWindow::onQualityCheckFinished);

    auto future = QtConcurrent::map(indices, computeFunc);
    watcher->setFuture(future);
}
1
2
3
4
5
6
7
8
9
10
11
12

第3部分:线程同步原语 ​

3.1 QMutex + QMutexLocker ​

cpp
// ===== 保护共享网格数据 =====
class SharedMeshData {
public:
    void updateNode(int nodeId, double x, double y, double z) {
        QMutexLocker lock(&m_mutex);  // 自动lock,作用域结束自动unlock
        m_nodes[nodeId] = {x, y, z};
    }

    Node getNode(int nodeId) const {
        QMutexLocker lock(&m_mutex);  // mutable mutex
        return m_nodes[nodeId];
    }

private:
    mutable QMutex m_mutex;
    std::vector<Node> m_nodes;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

3.2 QReadWriteLock —— 读写锁 ​

cpp
// ===== CAE读多写少场景 =====
class MeshResultCache {
public:
    // 可以多个线程同时读
    QVariant result(int nodeId) const {
        QReadLocker lock(&m_lock);  // 读锁,允许并发
        return m_cache.value(nodeId);
    }

    // 写时独占
    void updateResult(int nodeId, const QVariant& value) {
        QWriteLocker lock(&m_lock);  // 写锁,互斥
        m_cache[nodeId] = value;
    }

private:
    mutable QReadWriteLock m_lock;  // 比QMutex更适合读写场景
    QHash<int, QVariant> m_cache;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

3.3 QSemaphore —— 生产者消费者 ​

cpp
// ===== 求解器输出 → 解析 → UI(流水线)=====
class SolverPipeline {
    static constexpr int BUFFER_SIZE = 10;

    // 生产者(I/O线程):读取求解器输出
    void readOutput() {
        while (m_running) {
            QString line = readNextLine();  // 阻塞读
            m_freeSlots.acquire();   // 有空槽位?
            {
                QMutexLocker lock(&m_bufferMutex);
                m_buffer.enqueue(line);
            }
            m_usedSlots.release();  // 通知消费者
        }
    }

    // 消费者(UI线程):解析并更新进度
    void parseOutput() {
        while (m_running) {
            m_usedSlots.acquire();  // 有数据?
            QString line;
            {
                QMutexLocker lock(&m_bufferMutex);
                line = m_buffer.dequeue();
            }
            m_freeSlots.release();  // 通知生产者
            updateProgressFromLine(line);
        }
    }

    QSemaphore m_freeSlots{BUFFER_SIZE};
    QSemaphore m_usedSlots{0};
    QMutex m_bufferMutex;
    QQueue<QString> m_buffer;
    bool m_running = true;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

第4部分:CAE实战模式 ​

4.1 求解器后台运行——完整模式 ​

cpp
class SolverWorker : public QObject {
    Q_OBJECT
public slots:
    void run(const QString& solverPath, const QStringList& args) {
        QProcess proc;  // 在子线程创建,亲和性=子线程

        connect(&proc, &QProcess::readyReadStandardOutput, this, [&]() {
            QString out = proc.readAllStandardOutput();
            // 在线程内做正则解析
            parseProgress(out);
        });

        proc.start(solverPath, args);
        proc.waitForFinished(-1);  // 阻塞子线程,不阻塞UI线程!

        emit finished(proc.exitCode());
    }

signals:
    void progressUpdated(int percent, const QString& status);
    void finished(int exitCode);

private:
    void parseProgress(const QString& text) {
        static QRegularExpression rx(R"(progress\s*=\s*(\d+\.?\d*))");
        auto m = rx.match(text);
        if (m.hasMatch()) {
            double pct = m.captured(1).toDouble();
            emit progressUpdated(static_cast<int>(pct), text.trimmed());
        }
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

4.2 参数化扫描——多求解器并发 ​

cpp
void runParametricSweep(const QList<QStringList>& paramSets) {
    QAtomicInt completed{0};
    int total = paramSets.size();

    for (int i = 0; i < paramSets.size(); i++) {
        auto* thread = new QThread(this);
        auto* worker = new SolverWorker();

        worker->moveToThread(thread);

        connect(worker, &SolverWorker::finished, this,
                [&completed, total, this](int code) {
            int done = completed.fetchAndAddRelaxed(1) + 1;
            emit sweepProgress(done * 100 / total);
            if (done == total) emit sweepCompleted();
        });

        connect(worker, &SolverWorker::finished, thread, &QThread::quit);
        connect(thread, &QThread::finished, worker, &QObject::deleteLater);
        connect(thread, &QThread::finished, thread, &QObject::deleteLater);

        thread->start();
        QMetaObject::invokeMethod(worker, "run",
            Qt::QueuedConnection, Q_ARG(QString, solverPath),
            Q_ARG(QStringList, paramSets[i]));
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

第5部分:常见陷阱与调试 ​

5.1 经典崩溃 ​

cpp
// ❌ "Cannot send events to objects owned by a different thread"
void WrongThreadAccess() {
    SolverWorker* worker = new SolverWorker();
    QThread* thread = new QThread();
    worker->moveToThread(thread);
    thread->start();

    // ❌ 直接调用worker的方法 → 在主线程执行!
    worker->doWork("input.inp");  // 运行时警告:跨线程调用

    // ✅ 正确:用信号或QMetaObject::invokeMethod
    QMetaObject::invokeMethod(worker, "doWork",
        Qt::QueuedConnection, Q_ARG(QString, "input.inp"));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

5.2 调试工具 ​

bash
# Qt Creator → Debug → 查看线程面板(显示所有线程和每个QObject的位置)
# 运行时的线程检查
qDebug() << "Current thread:" << QThread::currentThread();
qDebug() << "Object thread:" << obj->thread();
1
2
3
4

核心总结 ​

总结1:QThread的正确姿势 ​

Worker (QObject, 无parent) → moveToThread(QThread)
→ QThread::started信号 → Worker::doWork槽(在子线程执行)
→ Worker::finished信号 → QThread::quit + deleteLater
事件驱动 Worker 优先 moveToThread;自包含任务可重写 run()。
1
2
3
4

总结2:并行层级选择 ​

场景工具粒度
单求解器后台运行QThread + Worker粗
并行网格操作QtConcurrent::map细
参数化扫描(N个求解器)N×QThread粗
任务队列QThreadPool + QRunnable中

总结3:GUI线程铁律 ​

QWidget 及其子类只能在 GUI 线程操作。跨线程更新通常使用排队信号槽,也可使用 QMetaObject::invokeMethod 或投递自定义事件;核心要求是实际 UI 操作最终在 GUI 线程事件循环执行。互斥锁不能让 QWidget 变成线程安全。


章节测试 ​

测试1:QThread用法 ​

以下哪种QThread用法是正确的? A. 继承QThread并重写run(),用于自包含任务且不把QThread槽误当工作线程槽 B. 创建QObject Worker,moveToThread到QThread,通过信号槽驱动 C. 直接new QThread并调用它的start(),不传任何Worker D. 在QThread的构造函数中创建QProcess

测试2:跨线程UI更新 ​

工作线程需要更新进度条,正确做法是? A. 在工作线程直接调用 m_progressBar->setValue(50) B. emit一个信号,让主线程的槽函数更新进度条 C. 用QMutex保护进度条,然后工作线程直接操作 D. 用QTimer在工作线程轮询进度条状态

测试3:并行网格 ​

100万单元质量检查,下面哪个方案最值得作为可扩展的并行候选并通过基准验证? A. for循环逐个计算 B. QtConcurrent::map并行处理 C. 每个单元创建一个QThread D. 用QProcess调用外部脚本


参考答案 ​

测试1答案 ​

答案:A、B 都可能正确,取决于模型。事件驱动 QObject(计时器、Socket、需要排队槽)通常适合 Worker + moveToThread;自包含阻塞任务或自定义循环可以重写 run()。关键是 QThread 对象活在创建它的线程,而 run() 在新线程执行。

测试2答案 ​

答案:B。跨线程信号槽自动使用QueuedConnection——emit在工作线程,槽函数在主线程事件循环中执行,安全更新UI。

测试3答案 ​

答案:B 是值得优先基准验证的方案,但“最快”不能脱离任务粒度、内存带宽、线程池配置和数据布局断言。A 可能在任务很轻或受内存带宽限制时更快;C 明显不可扩展;D 适合隔离的外部计算而非细粒度单元任务。


相关笔记 ​

  • Signal Slot Deep Dive - QueuedConnection的底层机制
  • Core Api Essentials - QProcess求解器启动
  • Solver Basics - 求解器基础

下一步学习 ​

  • [ ] 阅读 06 - CAE软件架构实战模式
  • [ ] 阅读 07 - 3D可视化与交互
  • [ ] 在你项目中用Worker+moveToThread模式重构一个后台任务

学习状态:🟡 开始学习

GUI、Worker 与结果提交图 ​

text
GUI/Document rev 8
 -> submit Task(id=41, rev=8, value snapshot)
Worker thread
 -> compute chunks + check cancellation
 -> emit Result(id=41, rev=8)
GUI queued receiver
 -> document still same and rev==8 ? commit : discard/rebase
1
2
3
4
5
6
7

线程安全只是底线;revision 校验解决“没有 data race 但结果过期”的逻辑竞态。

关闭等待图 ​

text
GUI requests stop
Worker blocked on queue/I/O ----must be woken/cancelled
Worker finishes -> QThread quit
GUI wait returns -> resources can die
1
2
3
4

若 GUI 持有 worker 需要的锁再 wait,会形成关闭死锁;wait 前释放业务锁,并让 worker 完成路径不依赖已停止的 GUI 同步调用。

面试连续追问 ​

问:QThread::quit 能停止正在运行的长函数吗? 答:不能,它请求事件循环退出;长函数不返回时需要协作取消/分块。

问:QueuedConnection 是否自动让共享数据安全? 答:它提供消息投递顺序,但参数外部指针、共享对象和多生产者业务不变量仍需所有权/同步。

问:为什么不建议 worker 直接修改 Document? 答:Document/UI 线程会同步读取,且结果可能过期;worker 生成独立结果,GUI 线程验证并事务提交更清楚。

自测与答案 ​

  1. 取消 flag 用普通 bool 可以吗? 答:跨线程不可以,应原子或锁保护。
  2. 线程池任务捕获 this 的风险? 答:任务晚于对象析构产生 UAF。
  3. progress 为什么要节流? 答:高频 signal 会淹没 GUI event queue 并拖慢任务与界面。

最后更新于:

Pager
上一篇5. Model/View架构与CAE数据处理 / Model-View Architecture and CAE Data Processing
下一篇7. CAE软件架构实战模式 / Practical Architecture Patterns for CAE Software

持续记录,持续成长

Copyright © Tidenflow