qt 线程、信号、槽、触发的槽函数在那个线程运行

1.问题确认

1.1 关系槽函数在那个线程运行的问题?

关系槽函数在那个线程运行的问题,无非两中可能;再发送信号的线程,在槽函数的线程。

默认是那个线程发送的信号,就运行在那个线程。如下举例:

在发信号的线程

QObject::connect(&sender, &Sender::aSignal, &receiver, &Receiver::aSlot, Qt::DirectConnection); 

在执行槽函数的线程

QObject::connect(&sender, &Sender::aSignal, &receiver, &Receiver::aSlot, Qt::QueuedConnection);

qt,提供了多种控制槽函数执行线程的 手段,但是上面两种是最基础的,也是最实用。 

1.2 如何控制槽函数在那个线程运行呢?

关于这个问题,其实线程的本质是什么,就是run函数;就是在那个线程的run函数里运行,就运行在那个线程。那么如何让函数执行在那个线程呢?其实run函数里一直值执行一个队列中的函数,只要有他就执行。这里再说说,信号,信号的发送就是把一个函数,和这个函数需要的参数放到这个线程执行的队列中,发到那个队列中,最终就在那个线程下运行,所以控制槽函数的运行线程的本质是把函数放到那个队列。

1.3 关系槽函数在那个线程运行的其他情况:qt 槽函数执行,所在的线程由什么决定,都有几种情况,请详细说明 

在Qt中,槽函数执行的线程主要取决于信号发出的线程以及信号与槽的连接类型。Qt的信号与槽机制允许在不同的线程之间安全地传递消息,而槽函数执行的线程取决于几个因素。以下是决定槽函数执行线程的不同情况:

  1. 直接连接(Qt::DirectConnection)
    • 当信号和槽在同一个线程中时,使用直接连接是最合适的。在这种情况下,槽函数将在信号发出的同一个线程中立即执行。
    • 如果信号和槽不在同一个线程,使用直接连接可能会导致不可预测的行为,因为它会直接在信号线程中调用槽函数,可能会违反线程安全。
  2. 队列连接(Qt::QueuedConnection)
    • 当信号和槽在不同的线程中时,队列连接是最常用的方式。当信号被触发时,槽函数的调用会被放入接收槽函数的对象所在线程的Qt事件队列中。这意味着槽函数将在接收对象的线程中异步执行,而不是在发出信号的线程中。
    • 如果信号和槽在同一个线程中,使用队列连接会导致槽函数的调用被延迟,直到当前线程的事件循环处理到该事件。
  3. 阻塞队列连接(Qt::BlockingQueuedConnection)
    • 这种连接类型与Qt::QueuedConnection类似,不同之处在于,当信号被触发时,发出信号的线程会阻塞,直到槽函数在接收槽函数的对象所在线程中被执行完毕。
    • 这确保了信号的发送方知道槽函数已经处理完成,但也可能导致性能问题或死锁,如果槽函数执行时间过长或涉及到等待其他资源。
  4. 自动连接(Qt::AutoConnection)
    • Qt::AutoConnection是默认的连接类型。如果信号和槽在同一个线程中,它的行为就像Qt::DirectConnection;如果它们在不同的线程中,它的行为就像Qt::QueuedConnection。
    • 这种连接类型为开发者提供了一种简便的方式,无需显式指定连接类型,Qt会根据信号和槽的线程上下文自动选择合适的行为。
  5. 唯一线程连接(Qt::UniqueConnection)
    • Qt 5.2及更高版本引入了Qt::UniqueConnection类型,这不是一个独立的连接类型,而是可以与其他连接类型(如Qt::QueuedConnection | Qt::UniqueConnection)组合使用的标志。
    • 当使用这个标志时,Qt会确保对于同一个信号和槽对,只存在一个连接。如果尝试多次连接同一个信号和槽,Qt会忽略后续的连接请求。这有助于防止意外地多次连接同一个信号和槽。

总的来说,槽函数执行的线程主要取决于信号发出的线程、槽函数所属的对象所在的线程以及信号与槽的连接类型。在设计多线程Qt应用程序时,正确选择和使用这些连接类型至关重要,以确保线程安全和程序的正确行为。

1.4 直接连接(Qt::DirectConnection)和,队列连接(Qt::QueuedConnection)举例

在Qt中,Qt::DirectConnection 和 Qt::QueuedConnection 是信号与槽连接的两种主要方式,它们决定了槽函数是如何在信号触发后被调用的。以下是这两种连接类型的例子:

Qt::DirectConnection 示例

#include <QObject>  
#include <QDebug>  
  
class Sender : public QObject {  
    Q_OBJECT  
public:  
    void emitSignal() {  
        emit aSignal();  
    }  
  
signals:  
    void aSignal();  
};  
  
class Receiver : public QObject {  
    Q_OBJECT  
public slots:  
    void aSlot() {  
        qDebug() << "Slot called in thread" << QThread::currentThreadId();  
    }  
};  
  
int main() {  
    Sender sender;  
    Receiver receiver;  
  
    QObject::connect(&sender, &Sender::aSignal, &receiver, &Receiver::aSlot, Qt::DirectConnection);  
  
    // 假设这里是在主线程中  
    sender.emitSignal(); // 直接触发槽函数,因为使用了Qt::DirectConnection  
  
    return 0;  
}  
  
#include "main.moc"

在这个例子中,Sender 类发出一个信号,而 Receiver 类有一个槽函数来响应这个信号。通过 Qt::DirectConnection,当 sender.emitSignal() 被调用时,Receiver::aSlot 会立即在发出信号的同一个线程中被调用。

Qt::QueuedConnection 示例

#include <QObject>  
#include <QThread>  
#include <QDebug>  
  
class WorkerThread : public QThread {  
    Q_OBJECT  
public:  
    void run() override {  
        emit workStarted();  
        qDebug() << "Worker thread running in thread" << QThread::currentThreadId();  
        exec(); // 进入事件循环  
    }  
  
signals:  
    void workStarted();  
};  
  
class MainObject : public QObject {  
    Q_OBJECT  
public slots:  
    void onWorkStarted() {  
        qDebug() << "Work started signal received in thread" << QThread::currentThreadId();  
    }  
};  
  
int main() {  
    WorkerThread workerThread;  
    MainObject mainObject;  
  
    QObject::connect(&workerThread, &WorkerThread::workStarted, &mainObject, &MainObject::onWorkStarted, Qt::QueuedConnection);  
  
    workerThread.start(); // 启动新线程  
  
    return workerThread.exec(); // 进入主线程事件循环  
}  
  
#include "main.moc"

在这个例子中,WorkerThread 类继承自 QThread 并发出一个 workStarted 信号。MainObject 类有一个槽函数 onWorkStarted 来响应这个信号。当 WorkerThread 的 run 方法被调用时(在新线程中),它会发出 workStarted 信号。由于使用了 Qt::QueuedConnectionMainObject::onWorkStarted 槽函数会在接收对象(mainObject)所在线程(这里是主线程)的事件循环中被调用。这意味着信号发出和槽函数调用是异步的,槽函数会在主线程的事件循环中排队等待执行。

2.线程ID确认的几个实验

2.1 qt 6.7 获取线程id,并打印出来

在 Qt 6.7 中,获取并打印线程ID可以通过使用 QThread::currentThreadId() 函数来实现。这个函数返回一个 quintptr 类型的值,代表当前线程的ID。你可以使用 qDebug() 或者其他打印函数来输出这个线程ID。

以下是一个简单的示例代码,展示如何在 Qt 6.7 中获取并打印当前线程的ID:

#include <QCoreApplication>  
#include <QDebug>  
#include <QThread>  
  
int main(int argc, char *argv[]) {  
    QCoreApplication app(argc, argv);  
  
    // 打印主线程的ID  
    quintptr mainThreadId = QThread::currentThreadId();  
    qDebug() << "Main Thread ID:" << mainThreadId;  
  
    // 创建一个新线程并打印其ID  
    QThread thread;  
    QObject::connect(&thread, &QThread::started, [&]() {  
        quintptr newThreadId = QThread::currentThreadId();  
        qDebug() << "New Thread ID:" << newThreadId;  
    });  
    thread.start();  
  
    return app.exec();  
}

这段代码首先打印出主线程的ID,然后创建一个新线程,并在新线程启动后打印其ID。注意,每次运行程序时,线程ID可能会不同。

此外,虽然 QThread::currentThreadId() 在大多数情况下足够使用,但如果你需要更底层的线程信息,或者与操作系统级别的线程ID进行交互,你可能需要使用特定于平台的API来获取这些信息。在Windows上,你可以使用 GetCurrentThreadId() 函数;在类Unix系统上,你可能需要使用 pthread_self() 或类似的方法来获取线程ID。然而,在Qt中,通常推荐使用 QThread::currentThreadId() 以保持跨平台的兼容性。

2.2 在 Qt 6.7,创建两个线程,并分别打印出两个线程的id 

在 Qt 6.7 中,你可以通过继承 QThread 类并重写其 run() 方法来创建线程。为了打印出两个线程的 ID,你可以在每个线程的 run() 方法中使用 QThread::currentThreadId() 函数。

以下是一个简单的示例代码,展示了如何创建两个线程并分别打印出它们的 ID:

#include <QCoreApplication>  
#include <QDebug>  
#include <QThread>  
  
class MyThread : public QThread {  
    Q_OBJECT  
public:  
    void run() override {  
        quintptr threadId = QThread::currentThreadId();  
        qDebug() << "Thread ID:" << threadId;  
    }  
};  
  
int main(int argc, char *argv[]) {  
    QCoreApplication app(argc, argv);  
  
    MyThread thread1;  
    MyThread thread2;  
  
    thread1.start();  
    thread2.start();  
  
    thread1.wait();  
    thread2.wait();  
  
    return 0;  
}

在这个示例中,我们定义了一个名为 MyThread 的类,它继承自 QThread 并重写了 run() 方法。在 run() 方法中,我们使用 QThread::currentThreadId() 获取当前线程的 ID,并使用 qDebug() 打印出来。

然后,在 main() 函数中,我们创建了两个 MyThread 的实例 thread1 和 thread2,并分别调用它们的 start() 方法来启动线程。最后,我们使用 wait() 方法等待两个线程执行完毕。

请注意,这个示例仅用于演示如何获取和打印线程 ID。在实际应用中,你可能需要处理更复杂的线程逻辑和同步问题。

2.3 线程A继承QThread,A有连个函数:fun1()和fun2(),如果在主线程M中调用A的fun2(),那么fun2会在那个线程中运行。 

在Qt中,当一个类继承自QThread并重写其run()方法时,通常只有run()方法中的代码会在新线程中执行。如果你在主线程M中直接调用继承自QThread的类A的fun2()方法,那么fun2()将在主线程中运行,而不是在新线程中。

这是因为直接调用类A的方法(如fun2())并不会触发线程的启动或切换。只有当调用start()方法时,Qt才会创建一个新线程并从run()方法开始执行。因此,除非fun2()是在run()方法内部被调用,否则它将在调用它的原始线程(在本例中是主线程M)中执行。

简而言之:

  • 如果你在主线程M中直接调用A的fun2(),则fun2()会在主线程中运行。
  • 如果你希望fun2()在新线程中运行,你需要在A的run()方法中调用fun2(),并且先调用A的start()方法来启动新线程。

这是一个重要的概念,因为它涉及到Qt中线程的工作方式以及如何在Qt中正确地使用线程。了解这一点有助于避免在多线程编程中遇到常见的错误和问题。

2.4 线程A继承QThread,A有连个槽函数:fun(),有一个主线程M,M有个信号fun(),绑定M的fun()和A的fun(),再M中发一个信号fun()调用A的fun(),这时候A的fun()是在那个线程中执行

在Qt框架中,当你将主线程M的信号fun()与线程A的槽函数fun()进行绑定,并在主线程M中发出fun()信号时,线程A的槽函数fun()将在线程A中执行,而不是在主线程M中。这是因为Qt的信号和槽机制是线程安全的,并且设计用于在对象之间进行跨线程通信。当信号被触发时,与之连接的槽函数将在接收信号的线程上下文中被调用。因此,在这个例子中,尽管信号是从主线程M发出的,但槽函数fun()将在线程A中执行。这种机制使得Qt中的多线程编程更加灵活和安全。 

2.5 线程A继承QThread,A有连个槽函数:fun(),有一个主线程M,M有个信号fun(),绑定M的fun()和A的fun(),再M中发一个信号fun()调用A的fun(),这时候A的fun()是在那个线程中执行,通过线程id区分函数在那个线程中运行,把这给例子的示例代码写出来 

在Qt中,当一个对象发出信号,连接到该信号的槽函数将在接收信号的对象的线程上下文中执行。在你的例子中,如果线程A(继承自QThread)已经启动并且正在运行,那么它的槽函数fun()将在线程A中执行,即使这个信号是从主线程M发出的。

以下是一个简单的示例代码,演示了如何创建这样的场景并打印出执行槽函数的线程ID:

#include <QCoreApplication>  
#include <QThread>  
#include <QDebug>  
#include <QObject>  
  
class Worker : public QObject {  
    Q_OBJECT  
public:  
    Worker() {}  
  
public slots:  
    void doWork(const QString &message) {  
        qDebug() << "Worker::doWork() executed in thread:" << QThread::currentThreadId()  
                 << "with message:" << message;  
    }  
};  
  
class Emitter : public QObject {  
    Q_OBJECT  
public:  
    Emitter(QObject *parent = nullptr) : QObject(parent) {  
        connect(this, &Emitter::workRequested, this, &Emitter::onWorkRequested, Qt::QueuedConnection);  
    }  
  
public slots:  
    void requestWork(const QString &message) {  
        emit workRequested(message);  
    }  
  
    void onWorkRequested(const QString &message) {  
        qDebug() << "Emitter::onWorkRequested() executed in thread:" << QThread::currentThreadId()  
                 << "with message:" << message;  
        // 这里可以放置处理工作的代码  
    }  
  
signals:  
    void workRequested(const QString &message);  
};  
  
int main(int argc, char *argv[]) {  
    QCoreApplication a(argc, argv);  
  
    QThread workerThread;  
    Worker *worker = new Worker();  
    Emitter *emitter = new Emitter();  
  
    worker->moveToThread(&workerThread);  
    QObject::connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);  
  
    // 将 Emitter 的信号连接到 Worker 的槽,使用 Qt::QueuedConnection  
    QObject::connect(emitter, &Emitter::workRequested, worker, &Worker::doWork, Qt::QueuedConnection);  
  
    workerThread.start();  
  
    // 发出信号  
    emitter->requestWork("Hello from main thread!");  
  
    // 等待线程完成工作并退出  
    workerThread.quit();  
    workerThread.wait();  
  
    delete emitter;  
  
    return a.exec();  
}  
  
#include "main.moc"

在这个例子中,Worker 类继承自 QObject 和 QThread。它有一个槽函数 fun(),当这个函数被调用时,会打印出当前线程的ID。MainObject 类有一个信号 fun(),它与 Worker 的槽函数 fun() 相连接。当 MainObject 发出 fun() 信号时,尽管这个信号是在主线程中发出的,但 Worker::fun() 槽函数将在 Worker 线程中执行,因为 Worker 线程已经启动并且正在运行。

注意:在实际应用中,通常不推荐直接从 QThread 继承并实现工作逻辑,而是应该将工作对象移动到新线程中。这里的示例是为了直接回答您的问题而设计的。在实际项目中,请考虑使用更现代的Qt并发模式,如 QtConcurrent 或将工作对象移动到 QThread 实例中。

运行结果

Emitter::onWorkRequested() forwarding message to worker: "Hello from main thread!"
Worker::doWork() executed in thread: 0xe9c4 with message: "Hello from main thread!"
Worker thread has finished.

2.5.2 修正代码

 

为了完善代码并确保其正常运行,我们需要关注几个关键点:

  1. 确保线程安全地启动和结束。
  2. 确保资源(如动态分配的对象)被正确管理,避免内存泄漏。
  3. 添加适当的错误处理和日志记录,以便于调试和后期维护。

下面是一个完善后的代码示例:

#include <QCoreApplication>  
#include <QThread>  
#include <QDebug>  
#include <QObject>  
  
class Worker : public QObject {  
    Q_OBJECT  
public:  
    Worker() {}  
  
public slots:  
    void doWork(const QString &message) {  
        qDebug() << "Worker::doWork() executed in thread:" << QThread::currentThreadId()  
                 << "with message:" << message;  
        // 模拟工作耗时  
        QThread::sleep(1);  
    }  
};  
  
class Emitter : public QObject {  
    Q_OBJECT  
public:  
    Emitter(QObject *parent = nullptr) : QObject(parent) {  
        // 无需使用Qt::QueuedConnection,因为信号和槽在不同线程时默认就是队列连接  
        connect(this, &Emitter::workRequested, this, &Emitter::onWorkRequested);  
    }  
  
public slots:  
    void requestWork(const QString &message) {  
        emit workRequested(message);  
    }  
  
    void onWorkRequested(const QString &message) {  
        qDebug() << "Emitter::onWorkRequested() forwarding message to worker:" << message;  
        emit workRequestedForWorker(message); // 假设这是转发给Worker的信号  
    }  
  
signals:  
    void workRequested(const QString &message);  
    void workRequestedForWorker(const QString &message); // 假设这是专门给Worker的信号  
};  
  
int main(int argc, char *argv[]) {  
    QCoreApplication a(argc, argv);  
  
    QThread workerThread;  
    Worker *worker = new Worker();  
    Emitter *emitter = new Emitter();  
  
    // 将 Worker 对象移动到新线程  
    worker->moveToThread(&workerThread);  
  
    // 连接信号和槽,确保线程安全退出  
    QObject::connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);  
    QObject::connect(&workerThread, &QThread::finished, [](){  
        qDebug() << "Worker thread has finished.";  
    });  
  
    // 连接 Emitter 的信号到 Worker 的槽  
    QObject::connect(emitter, &Emitter::workRequestedForWorker, worker, &Worker::doWork);  
  
    // 启动线程  
    workerThread.start();  
  
    // 发出工作请求信号  
    emitter->requestWork("Hello from main thread!");  
  
    // 等待一段时间以确保工作完成,仅用于示例,实际应用中应使用更合适的同步机制  
    QThread::sleep(2); // 等待时间应大于 Worker 中模拟的工作耗时  
  
    // 安全退出线程  
    workerThread.quit();  
    workerThread.wait();  
  
    // 清理资源  
    delete emitter;  
  
    return a.exec();  
}  
  
#include "main.moc"

注意事项:

  • 我增加了一个假设的信号 workRequestedForWorker,用于从 Emitter 转发工作请求给 Worker。这样做是为了清晰地区分 Emitter 内部处理的信号和转发给 Worker 的信号。
  • 我添加了一个简单的同步机制(QThread::sleep(2)),仅用于示例目的,以确保 Worker 中的工作完成后才退出线程。在实际应用中,应该使用更可靠的同步方法,如信号和槽机制,来确保工作完成后再退出线程。
  • 在线程结束时,通过连接 QThread::finished 信号到 QObject::deleteLater 槽来确保 Worker 对象被正确删除,防止内存泄漏。
  • 增加了日志输出,以便于调试和监控程序运行状态。

2.5.3 这次的代码没有问题了,那么之前的那版代码没有任何输出的原因是啥呢 

之前版本的代码没有输出的原因可能有几个方面:

  1. 线程没有正确启动或没有足够的时间执行:如果workerThread.start();没有被调用,或者线程启动后没有足够的时间去执行任务就被要求退出了,那么线程中的工作可能根本没有机会执行,因此不会有任何输出。

  2. 信号与槽没有正确连接:如果信号和槽之间的连接没有正确设置,或者信号根本没有被触发,那么槽函数(在这个例子中是Worker::doWork)就不会被调用,因此也就不会有输出。

  3. 资源提前释放:如果在工作线程完成任务之前,相关的资源(如Emitter对象)被释放了,那么信号可能无法正确发送,或者发送到了已经不存在的对象上,导致没有输出。

  4. 线程同步问题:如果没有适当的同步机制,主线程可能在工作线程完成之前就已经结束了程序,这样工作线程的输出可能就不会被看到。

在你提供的原始代码片段中:

// 等待线程完成工作并退出    
workerThread.quit();    
workerThread.wait();    
  
delete emitter;

这段代码直接调用了workerThread.quit();来尝试结束线程,但如果没有确保线程中的任务已经完成,就可能导致线程中的工作被提前终止。紧接着调用workerThread.wait();会阻塞主线程,直到工作线程退出。然而,如果工作线程中的任务因为quit()调用而被中断,那么它可能根本就没有机会执行任何输出操作。

此外,如果emitter对象是在线程任务开始之前或之中被删除的(尽管在你给出的代码片段中没有显示这部分),那么它发出的信号将不会被处理,因为相关的槽可能已经被销毁或不再有效。

综上所述,之前版本的代码没有输出的原因可能是线程管理不当、信号与槽连接问题、资源提前释放或线程同步问题。在我给出的修改后的代码中,这些问题都得到了相应的处理,从而确保了程序的正确输出。

2.6 Qt::BlockingQueuedConnection举例 

Qt::BlockingQueuedConnection 是 Qt 框架中信号和槽机制的一个连接类型。这种连接类型的特点在于,当信号被触发时,发送信号的线程会阻塞,直到槽函数在接收信号的对象的线程中执行完成。这种连接类型主要用于跨线程通信,并确保信号发送方知道槽函数已经处理完毕。

以下是一个使用 Qt::BlockingQueuedConnection 的简单示例:

 

在这个例子中,我们创建了一个 Worker 类和一个 Signaler 类。Worker 类有一个槽函数 doWork,它接收一个字符串消息并打印它,然后模拟一个耗时的操作。Signaler 类有一个信号 workRequested 和一个槽函数 emitSignal,后者被调用时会触发 workRequested 信号。

在 main 函数中,我们创建了一个 QThread 实例,并将 Worker 对象移动到该线程中。然后,我们使用 Qt::BlockingQueuedConnection 连接 Signaler 的 workRequested 信号到 Worker 的 doWork 槽函数。这意味着当 workRequested 信号被触发时,发送信号的线程(在这里是主线程)会阻塞,直到 doWork 槽函数在 workerThread 中执行完成。

请注意,由于 Qt::BlockingQueuedConnection 会阻塞信号发送的线程,因此在设计多线程应用程序时需要谨慎使用,以避免死锁或性能瓶颈。通常,更推荐使用 Qt::QueuedConnection(默认跨线程连接类型),它不会阻塞信号发送的线程。

 

  • 45
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值