【Qt线程-6】获取当前线程id,thread()和currentThreadId(),不是想当然那样,不使用信号槽可能看不出区别

背景:

本人学习qt多线程的一些记录:

【Qt线程-1】this,volatile,exec(),moveToThread()_qt线程exec_大橘的博客-CSDN博客

【Qt线程-2】事件循环(QCoreApplication::processEvents,exec)的应用_大橘的博客-CSDN博客

【Qt线程-3】使用事件循环,信号,stop变量,sleep阻塞,QWaitCondition+QMutex条件变量,退出子线程工作_qt子线程死循环退出还能进么_大橘的博客-CSDN博客

【Qt线程-4】事件循环嵌套,BlockingQueuedConnection与QWaitCondition比较_大橘的博客-CSDN博客

【Qt线程-5】生产者&消费者模型应用(多态,子线程控制,协同,事件循环)_qt生产者消费者模型_大橘的博客-CSDN博客

本次要记录的,主要是针对概念的再次冷静并深入的理解。所以一开始还是要必须强调概念。

概念——线程与线程对象:

通常我们通过一个QThread类创建一个线程,无论是重写run函数还是moveTothread,都要用到QThread类。但是当写QThread *thd = new QThread的时候,切记,这里新建的thd,仅仅是当前线程(父线程)当中的一个对象而已,它仅仅是个对象指针,用于操作子线程,或者可以理解为某种句柄,它仅仅是个入口,但是它代表不了真正意义上的子线程。

比如,当希望输出父线程和子线程的id看看是否确实不是一个线程的时候,下面这样写有好多问题:

(假设这些代码直接写在父线程中,this是父线程的本地对象指针,obj是一个放在子线程里的对象指针)

    QThread *thd = new QThread;
    thd->start();

    qDebug() << "main_thread: ptr=" << this->thread() << ", id=" << this->thread()->currentThreadId()
             << ", QThread::currentThreadId()=" << QThread::currentThreadId();

    C *obj = new C;
    qDebug() << "Before moveToThread:";
    qDebug() << "sub_thread: ptr=" << obj->thread() << ", id=" << obj->thread()->currentThreadId()
             << ", QThread::currentThreadId()=" << QThread::currentThreadId();

    obj->setParent(nullptr);
    obj->moveToThread(thd);

    qDebug() << "After moveToThread:";
    qDebug() << "sub_thread: ptr=" << obj->thread() << ", id=" << obj->thread()->currentThreadId()
             << ", QThread::currentThreadId()=" << QThread::currentThreadId();

首先QThread::currentThreadId()肯定都是一样的,相当于输出的都是父线程id。因为都是在父线程调用的这句代码。

直接输出thread()确实是不一样的。在网上我见过不少代码都是这样讲解的。因为thread()函数返回的是对象指针,也就是那个thd对象,输出thread()等于输出线程对象的地址,地址不一样就是线程不一样。

但是输出thread()->currentThreadId()却看不出区别。因为thread()返回的是thd这个线程对象,而它是生存在父线程的,所以最终输出的还是父线程id。

概念——qt线程安全:

按照qt手册的说法,信号槽是保证线程安全的,或者说它是qt保证线程安全的重要方式。像上面的代码,如果给obj的类C添加一个公有函数,在里面输出线程id,像这样:

class C : public QObject
{
    Q_OBJECT
public:
    void f () { qDebug() << "func: " << this->thread()->currentThreadId() << ", " << this->thread() << ", " << QThread::currentThreadId(); }

};

在父线程强行调用它,obj->f();输出还是看不出子线程id的不一样。直接调用就好比单线程执行,有点像connect函数的directconnection。

所以按照qt的惯例,使用信号槽处理异步队列。给上面的类C加一个槽函数,给父类加一个信号,然后绑定它们。最后父类发信号,让子类异步响应,再输出子线程的id。像下面这样:

class C : public QObject
{
    Q_OBJECT
public:
    int m_i = 0;
    void f () { qDebug() << "func: " << this->thread()->currentThreadId() << ", " << this->thread() << ", " << QThread::currentThreadId(); }

public slots:
    void on() { qDebug() << "slot: " << this->thread()->currentThreadId() << ", " << this->thread() << ", " << QThread::currentThreadId(); }
};


namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

signals:
    void sig();

private:
    Ui::MainWindow *ui;
};
 QThread *thd = new QThread;
    thd->start();

    qDebug() << "main_thread: ptr=" << this->thread() << ", id=" << this->thread()->currentThreadId()
             << ", QThread::currentThreadId()=" << QThread::currentThreadId();

    C *obj = new C;
    qDebug() << "Before moveToThread:";
    qDebug() << "sub_thread: ptr=" << obj->thread() << ", id=" << obj->thread()->currentThreadId()
             << ", QThread::currentThreadId()=" << QThread::currentThreadId();

    obj->setParent(nullptr);
    obj->moveToThread(thd);

    qDebug() << "After moveToThread:";
    qDebug() << "sub_thread: ptr=" << obj->thread() << ", id=" << obj->thread()->currentThreadId()
             << ", QThread::currentThreadId()=" << QThread::currentThreadId();

    connect(this, &MainWindow::sig, obj, &C::on, Qt::BlockingQueuedConnection);

    obj->f();
    emit sig();

上面connect函数我用了BlockingQueuedConnection连接方式,这里没什么意义,就是想试试效果。各种连接方式的区分,见另外一篇博客,尤其是DirectConnection方式,与Blocking方式想比,时序一样,但工作线程不同。

放在一起对比一下,最后输出:

主线程输出: 线程对象指针,thread线程id,QThread线程id:
main_thread: ptr= QThread(0x1bf34e20) , id= 0x2168 , QThread::currentThreadId()= 0x2168

子线程输出: 线程对象指针,thread线程id,QThread线程id:
Before moveToThread:
sub_thread: ptr= QThread(0x1bf34e20) , id= 0x2168 , QThread::currentThreadId()= 0x2168

After moveToThread:
sub_thread: ptr= QThread(0x1d572fa0) , id= 0x2168 , QThread::currentThreadId()= 0x2168

func:  0x2168 ,  QThread(0x1d572fa0) ,  0x2168
slot:  0x1e8c ,  QThread(0x1d572fa0) ,  0x1e8c

很明显是不一样的。其中:

线程对象区别明显,只要不是一个线程,对象地址必然不同。

thread()->currentThreadId()因为通过thread()实现,一定要知道thread()虽然可以地址不一样,但毕竟只是父线程里的一个指针变量而已,它的生存线程必然是父线程。所以始终看不出区别。除非使用信号槽异步查看。(但是很有意思,有无发现使用信号槽以后,虽然还是从thread()调用currentThreadId(),但返回值却可以体现子线程了。但是thread()输出的还是父线程那个指针变量。所以玄机还是在currentThreadId()?)

QThread::currentThreadId()可以输出当前线程id,但通过函数直接调用是不行的,等于还是在父线程执行。只有通过信号槽异步才正确。

结论:

直接使用thread()函数输出指针,可以用,反正就是个人极度不爽。还是因为它仅仅是个指针,它代表不了真正意义上的子线程过程(run函数)。这个需要我自己转变对它的态度。

使用currentThreadId()一定要配合信号槽,让qt自己处理异步队列,否则不行。另外,这个id也不是线程真正的id,qt手册提到过,不建议程序里这样用。调试用用即可。

你可能感兴趣的:(qt/c++,qt,开发语言,c++)