背景:
个人学习多线程控制,写了一些博文用于记录:
【Qt线程-1】this,volatile,exec(),moveToThread()
【Qt线程-2】事件循环(QCoreApplication::processEvents,exec)的应用
【Qt线程-3】使用事件循环,信号,stop变量,sleep阻塞,QWaitCondition+QMutex条件变量,退出子线程工作
【Qt线程-4】事件循环嵌套,BlockingQueuedConnection与QWaitCondition比较
【Qt线程-5】生产者&消费者模型应用(多态,子线程控制,协同,事件循环)
【Qt线程-6】获取当前线程id,thread()和currentThreadId(),不是想当然那样,不使用信号槽可能看不出区别
线程对象和线程执行
两者不是一回事。很多人说法不同,但都是一个意思。为了叙述方便,姑且称之为子线程,因为所有多线程处理,都是相对于主线程而言的。
比如在主线程中新开一个线程,需要类似这样的操作:
QThread *thd = new Qthread;
当然有时候是从Thread派生一个自定义的线程类。就如在主线程里创建一个对象没两样。
所以thd仅仅是主线程中的一个对象,thd这个对象,是用来管理子线程的。亦即,谁要操作这个子线程,必定要通过thd来操作,比如:
thd.start();
thd.stop();
thd.quit();
thd.wait();
thd.deleteLater();
...
就如操作某个控件一样来操作子线程。有点类似于windows的句柄。
因此,在定义thd的类代码中,所有成员变量和函数,都是在thd这个对象当中。如果没有run函数的参与,都会运行于主线程中。例如写个槽函数之类,相当于还是运行于主线程。
千万不能认为发给thd一个信号,只要它响应就应该是运行于子线程,这是不对的。
善用QDebug
在恰当位置输出信息,比如:
qDebug() << "frmMain::f_ClientConnect():The main thread is:"
<< this->thread()->currentThreadId();
qDebug() << "ThreadConn::f_ConnectCome() runs on thread:"
<< this->currentThreadId();
qDebug() << "ThreadConn::f_ConnectCome():tcpSocket runs on thread:"
<< m_tcpSocket->thread()->currentThreadId();
在调试时可以随时看到指定位置跑在哪个线程。
run()函数
run函数相当于真正线程过程的入口。所以通过run启动执行的代码,才是跑在子线程中。run函数调用的所有代码执行完,线程就完了。而这些通过run调用的代码,是可以使用thd类中定义的变量的。
this指针
在thd子线程的类代码中,this指针指向的是thd对象,它的parent是主线程,所以this其实是工作于主线程的。这点必须要理解,不是说在线程代码中使用this,就表示子线程了。
举个例子,如果在子线程中新建一个对象,像这样:
QObject *obj = new QObject(this);
开始我这样写也觉得没什么不妥,后来发现会报错的。思考之后才知道不能这样写,因为这相当于在子线程里创建一个属于主线程的对象,于是会有类似如下这样的提示:
" Cannot create children for a parent that is in a different thread.
所以如果真的需要在子线程创建对象,去掉那个this就好了,如:
QObject *obj = new QObject();
exec和moveToThread
run函数中写个exec,是系统的事件循环,类似于死循环,但它占用资源很小。它可以让run保持运行,不是阻塞,它可以随时接收系统事件,但这个事件不是随便发个信号它就能响应的。
如果一个线程已经在运行,某个时刻希望有代码加入到exec的队列中,就写个子类,定义好响应信号的槽函数。实例化对象,比如叫obj,使用obj.moveToThread(thd)将它移动到子线程thd。然后给obj发信号让它响应就可以了。此时,obj当中的槽响应代码是可以加入exec的响应队列,它是在子线程中执行的。
Qt官方帮助这样写:
void QObject::moveToThread(QThread *targetThread)
Changes the thread affinity for this object and its children. The object cannot be moved if it has a parent. Event processing will continue in the targetThread.
……
我英文就不好,但是从学习linux和qt以来,发现有些帮助写的其实挺好理解的,主要是这些词都认识,不认识就百度翻译,见多就记住了。
volatile
这是个很好的关键字,看操作系统教科书上的代码,总有while (true){}这种死循环,然后通过判断某个变量决定是否跳出。但是单线程里直接这样写就真的死循环了。好像那个作为判断条件的变量不是那么理想化的。
线程代码里定义某个变量当做标记时,加上volatile关键字,例如这样:
volatile bool b = false;
这样它就听话了。至于为什么,直接网上搜索关键字volatile,各种好文章说的很到位。有位朋友这样写:
“线程之间的可见性,一个线程修改的状态对另一个线程可见,也就是一个线程修改的结果,另一个线程马上就能看到。”
已经很好理解了。这属于内存问题,如果不想深入理解,会用即可。
其实我试过,不用volatile貌似也能用。但有些时候又是必要的,所以是否需要使用,自己斟酌。
参考:C/C++ 中 volatile 关键字详解https://www.runoob.com/w3cnote/c-volatile-keyword.html