- QFuture:表示异步计算的结果
- QFutureWatcher:QFuture本身不带信号槽,可使用QFutureWatcher进行监控QFutureWatcher 接收该事件后发送对应信号。
//concurrent生成future
QFuture<void> future = QtConcurrent::run([this]{ /**/ });
//future设置给watcher进行监控
QFutureWatcher<void> theWatcher;
theWatcher.setFuture(future);
//future对应的操作结束,触发watcher信号
connect(&theWatcher,&QFutureWatcher<void>::finished,this,[this]{ /**/ });
QFuture
用于表示异步操作的结果,可以通过QFutureWatcher
来监控和获取结果。QConcurrent
提供了一些函数和工具来简化并发编程,比如QtConcurrent::run
和QtConcurrent::map
。
QThreadPool线程池默认最大线程数,也是CPU逻辑Core的数量。
简便性:QtConcurrent::run
是一个简单的接口,适合快速启动一个新的任务。它会自动使用全局的 QThreadPool
。
QThreadPool
允许你创建一个线程池,可以重用线程,减少频繁创建和销毁线程的开销。
Qt 应用程序中的所有事件都被存储在一个事件队列中,这个队列由 QCoreApplication 管理。
事件循环是一个持续运行的循环,它不断地从事件队列中取出事件,并将其分发到合适的处理对象。
当调用 deleteLater()
时,Qt 将删除请求排入事件队列,而不是立即删除对象
如果在事件循环真正销毁对象的过程中,其他线程仍然在访问它,就会出现崩溃或未定义行为,因为对象的内存可能已经部分或完全释放了。
1. 线程同步:使用互斥锁 (Mutex) 或信号槽机制
QMutex mutex;
// In the thread:
mutex.lock();
// Access global variable
mutex.unlock();
2. 使用 QPointer
来监测对象的状态
QPointer<MyClass> myObjectPointer = new MyClass;
// 在线程中访问时:
if (myObjectPointer) {
// 安全访问对象
myObjectPointer->someMethod();
}
3.避免线程访问已被标记删除的对象
在对象销毁之前,通知其他线程停止访问该对象。可以在对象的析构函数中发送信号,告知其他线程不再访问该对象。
在调用析构函数的过程中,类的成员变量不会被立即释放,直到析构函数执行完毕。因此,在析构过程中,你可以安全地访问和修改这些成员变量。
public:
~MyClass() {
emit aboutToBeDeleted(); // 在析构前发出信号
}
bool is_delete_ = false;
// 连接信号和槽
QObject::connect(myObject.get(), &MyClass::aboutToBeDeleted, [](){
is_delete_ = true;
});
4. 使用智能指针管理对象生命周期
QPointer所指向的对象必须是QObject或其派生类对象。 因为其对象析构时会执行QObject的析构函数,
if (exec_sql_watcher_->future().isRunning()) {
exec_sql_watcher_->future().cancel(); // 取消任务
exec_sql_watcher_->future().waitForFinished(); // 等待任务结束
// 后续操作需要确保任务已完成
}
thread.requestInterruption(); // 请求线程中断
if (QThread::currentThread()->isInterruptionRequested()) {
qDebug() << "Thread interrupted, exiting...";
return; // 优雅地退出
}
QElapsedTimer timer;
timer.start();
// 获取执行时间(以毫秒为单位)
qint64 elapsedTime = timer.elapsed();
qDebug() << "Code executed in" << elapsedTime << "milliseconds.";
- 悬挂指针:指针指向已经释放的内存,尽管指针本身是有效的,但所指向的内容不再有效。
- 自动化内存管理:使用智能指针(如
QSharedPointer
或QPointer
)可以有效管理对象的生命周期,并在对象被删除后自动将指针设置为nullptr
,从而避免悬挂指针的问题。
多个线程会存在同时对同一个数据进行修改操作的现象就叫做数据竞争
数据竞争的影响
- 不一致的结果:由于线程执行的顺序不同,最终的结果可能会不一致。
- 难以调试:数据竞争问题常常难以重现和定位,因为它们依赖于线程的调度。
- 潜在的崩溃:在某些情况下,数据竞争可能导致程序崩溃或异常行为。
为什么多线程插入数据加锁时比不加锁运行时间快?
1. 消除数据竞争
在没有加锁的情况下,线程可能会发生数据竞争,造成 CPU 频繁同步内存或重新加载缓存行,这会导致显著的性能问题。
2. 线程调度的优化
多个线程可能会不断地争夺同一块缓存行的所有权,这会降低缓存效率,进而影响程序性能。
3.减少上下文切换
- 在不加锁的情况下,如果数据竞争频繁,可能导致线程发生更多的上下文切换(线程切换)。上下文切换会耗费 CPU 时间,影响整体性能。
- 加锁后,线程之间的调度更加有序,减少了上下文切换的次数,从而提高了执行效率。
QSharedPointer<QFutureWatcher<void>> watcher = QSharedPointer<QFutureWatcher<void>>::create();
// 创建一个 QFutureWatcher<void> 并用 QSharedPointer 管理 QSharedPointer<QFutureWatcher<void>> watcher(new QFutureWatcher<void>);
// 创建 QSharedPointer 指向 QFutureWatcher
QSharedPointer<QFutureWatcher<void>> watcher = QSharedPointer<QFutureWatcher<void>>::create();
// 捕获指针并在 finished 信号触发时删除它
QObject::connect(watcher.get(), &QFutureWatcher<void>::finished, [=]() mutable {
qDebug() << "Future has finished processing!";
// 删除内部的指针
watcher.reset(); // 调用 reset() 将 QSharedPointer 内部的指针删除,并将其设为 nullptr
if (!watcher) {
qDebug() << "Watcher has been deleted and set to nullptr.";
}
});
// 启动你的任务
QFuture<void> future = QtConcurrent::run([]() {
// 模拟长时间运行的任务
QThread::sleep(2); // 等待2秒
});
watcher->setFuture(future); // 设置 Future,开始跟踪任务进度
-
mutable
修饰符:当你使用 lambda 表达式时,默认情况下捕获的变量是只读的。如果你想修改捕获的变量(比如watcher
),需要在 lambda 中添加mutable
关键字。这样才能在匿名函数内部调用reset()
修改watcher
。 -
watcher.reset()
:调用reset()
方法会销毁QSharedPointer
内部的对象,并将其置为nullptr
。这样,当任务完成时,内部的指针将被安全地删除。
在多线程的 Qt 应用程序中,只能在主线程中更新 UI。这是因为 Qt 的 GUI 操作不是线程安全的,如果你在其他线程中直接操作 UI,可能会导致不稳定的行为、崩溃或其他异常问题。
要在多线程中更新 UI,正确的做法是通过信号-槽机制将数据从工作线程传递回主线程,然后让主线程负责更新 UI。