其实用法早就总结了,但是因为自身事务原因,一直没有比较他们间的效率,甚至各种互斥量,条件变量之间的效率,更多是集中看了下他们各自的api和特点,所以估计以后还需要补充或者是另开一篇来讲= =。
序
QThread的线程用法上与std::thread相比有较大的区别,4.4版本之前是继承的方式来使用线程(个人猜测可能是因为那会儿c++11还没出来,std::function和std::bind没有,所以继承是实现消息回调比较方便的方式,当然仅仅是猜测,有兴趣可以查证),但4.4之后开始,官方建议不要再用继承的方式来使用线程,而是通过信号槽的方式来取代。
测试环境:Qt5.7,vs2015。
一、QThread:
如下图所示:
看不清楚图可以直接在官网看:传送门
1、继承自QObject(截图没截上)
2、启动线程:start()函数,启动后调用run()函数,run()执行完之后退出线程。
3、wait:有点类似std::thread 的join,但是需要指定时间,并且不是线程run函数结束后自动返回,如果不指定,默认会一直等待。所以我一般在用的过程中,需要退出的时候(或者调用quit),再wait。
二、两种用法:
既然QThread有两种用法,那么就简单介绍下吧:
图里已经说得很清楚了,那我就不在多说:
头文件:
class QtThreadFuncByThread : public QThread
{
public:
void SetLoop(int loop);
int GetSum();
protected:
virtual void run();
private:
int _Loop = 0;
int _Sum = 0;
};
cpp
void QtThreadFuncByThread::SetLoop(int loop)
{
this->_Loop = loop;
}
int QtThreadFuncByThread::GetSum()
{
return this->_Sum;
}
void QtThreadFuncByThread::run()
{
for(int i = 0; i < this->_Loop; ++i)
{
this->_Sum++;
}
int id =(int)QThread::currentThreadId();
emit Log::GetInstance()->LogStr(QString("qt继承方式子线程id: %1;").
arg(id));
}
使用:
QtThreadFuncByThread thread;
thread.SetLoop(loop);
thread.start();
thread.wait(100);
2、信号槽方式(推荐用法):
至于为啥推荐呢,直接给个传送门吧:传送门
至于代码,官网给了例子:传送门
我再写个简化版的:
QThread * thread = new QThread;
QtThreadFuncClass* funcclass = new QtThreadFuncClass;
funcclass->SetLoop(loop);
funcclass->moveToThread(thread);
QObject::connect(thread, &QThread::started, funcclass, &QtThreadFuncClass::ThreadFunc, Qt::DirectConnection);
thread->start();
thread->wait(100);
简而言之,你需要做的是把QObject对象,movetothread去,否则你调用的信号槽仍然是在主线程。
二、互斥量,锁,条件变量,原子操作及其他:
其实std::thread有的那些互斥量,自解锁,条件变量,future,原子操作等,Qt里面也能找到对应的类,只是用法,和一些细节性的功能不太一致,其他大致都是相似的,所以我就简要的整理和汇总下:
1、互斥量与自解锁:
如下图所示:
qt的互斥量只有简单的QMutex,当然,某种程度上是std::thread里那几种结合体,自解锁也只有一种,如果不记得std::thread有哪些,请看我上一篇。用法比较简单,我就不贴代码了。
2、读写锁与其自解锁:
所谓读写锁,顾名思义,即读锁定状态与写锁定状态是不一样的。例如指定某段读取区域为lockForRead(),则表示这段代码仅仅是对资源进行读取,没有改变,所有线程可以共享该资源,无需阻塞;另一段改写某资源区域为LockForWrite(),则表示这段代码需要改写资源,其他线程需要阻塞,同时,lockForRead()锁定的读取资源代码也会被锁住,别的线程无论是读还是写会阻塞。
3、信号量:
图中其实把函数介绍的比较清楚了,即先指定一定数量的信号量,在实际用的过程中可用于一个类似生产者消费者模式,一个线程负责生产,如果信号量没有空位置了,rlease(加一个位置),另一个线程负责消费,如果有空位置,就消费一个(acquire)。
4、条件变量:
前面两个读写锁和信号量这些还和std::thread提供的机制有区别(因为没有,当然,想实现也可以用互斥量这些自己写一个)。条件变量几乎是没啥差异,用法也都差不多,函数已经在图中列出来了。
5、原子操作:
官网传送门:传送门
一共就如图所示只有三种,int,integer,pointer,用法也没太大的区别,不记得在上一篇有没有说过,原子对象本身也是带锁的,多线程访问的时候不用担心上锁问题,是为细粒度锁(mutex这些是粗粒度锁)。
6、future:
qfuture依赖于Qt Concurrent,下图接上图:
官网传送门:传送门
简单的说就是一个异步方法,类似std::async,只执行一次的异步方法,具体概念用法图中已经解释了。
下图解释下QFuture和QFutureWatcher关系(官网例子):
7、QThreadPool:
其实这个不算线程机制,不应该放上来,随便看看:
三、一张图:
该图能较好说明QThread的一些用法及生命周期。
如下图所示
参考资料:传送门
本次其实还有许多的东西可以说,只是做了个简单的图表,慢慢来,现在没有时间补充,总结完这些,其实理解上的话也比较简单,暂时就先这样,当做挖个坑吧= =。
ps:基本上,线程的一些特性就先这样,以后有时间再说,下期是网络编程,Qt的tcp和udp,以及asio的tcp,udp,时间未知。