QThread::wait(),一直以来我以为它阻塞的是QThread对象,可是我现在明白,原来阻塞的是这个对象所在的线程(通常是主线程)——所有事情源于 QThread 的事件循环——如果使用继承QThread这一方法,QThread::quit()没有效果,因为这个线程根本就不需要事件循环
近日,使用QThread,一些问题百思不得其解,看过大牛的文章,恍然大悟啊。
原文 http://hi.baidu.com/dbzhang800/item/c14c97dd15318d17e1f46f41
在文章开始之前加注一点,为和我一样Qt水平不高的朋友提醒一下。QThread::wait(),一直以来我以为它阻塞的是QThread对象,可是我现在明白,原来阻塞的是这个对象所在的线程(通常是主线程)。
bool QThread::wait ( unsigned long time = ULONG_MAX )
Blocks the thread until either of these conditions is met:
The thread associated with this QThread object has finished execution (i.e. when it returns from run()). This function will return true if the thread has finished. It also returns true if the thread has not been started yet.
time milliseconds has elapsed. If time is ULONG_MAX (the default), then the wait will never timeout (the thread must return from run()). This function will return false if the wait timed out.
以下红色部分为我添加。
昨天不小心看到Qt开发人员( Bradley T. Hughes)Blog中的一片文章 you are-doing-it-wrong 。 结果看得头昏脑胀:好歹也自学了近1年的Qt,也一直很小心、很认真地阅读Qt和manual和例子等资料,却被突然告知,QThread的正确使用方法是一种自己从没见过,而且Qt manual、example、书籍中都没有提到过的一种方法。到底怎么了...
莫非manual、exmaple以及资料中的介绍都是错的??
认真看看其他的人的评论,总算理清了一点头绪。所有事情源于 QThread 的事件循环!
QThread 的两种使用方法
1. 不使用事件循环。这是官方的 Manual 、example 以及相关书籍中都介绍的一种的方法。
a. 子类化 QThread
b. 重载 run 函数,run函数内有一个 while 或 for 的死循环
c. 设置一个标记为来控制死循环的退出。
2. 使用事件循环。(博客 you are-doing-it-wrong 批驳的就是这种情况下的 一种用法。)
a. 子类化 QThread,
b. 重载 run 使其调用 QThread::exec()
c. 并为该类定义信号和槽,这样一来,由于槽函数并不会在新开的 thread 运行,很多人为了解决这个问题在构造函数中调用
moveToThread(this);
而争论和不解正是这样的一条语句造成的。
Bradley T. Hughes 给出说明是: QThread 应该被看做是操作系统线程的接口或控制点,而不应该包含需要在新线程中运行的代码。需要运行的代码应该放到一个QObject的子类中,然后将该子类的对象moveToThread到新线程中。另外:
在Qt4.3(包括)之前,run 是虚函数,必须子类化QThread来实现run函数。
而从Qt4.4开始,qthreads-no-longer-abstract ,run 默认调用 QThread::exec() 。这样一来不需要子类化 QThread 了,只需要子类化一个 QObject 就够了,这正是被 Bradley T. Hughes推荐的方法。
终于看懂了,但
不管怎么说,都应该是 QThread 当初的设计导致的这种问题,而所有文档和例子中都没有提到该如何使用Qthread 进一步加剧了对QThread的这种误用。
另注:1.QThread对象从建立起就是活跃的,所以大牛Bradley T. Hughes把QObject对象移动到QThread中,对QObject的操作是完全合理合法合逻辑的。
3.使用大牛Bradley T. Hughesr的方法把QObject对象移动到QThread中,要使用signal+slot的方式来调用函数,这样的话,通过QT消息机制,QObject被调用的函数是在线程内执行。如果直接(QObject对象).abc()的话,这个成员函数是在主进程内执行,可能会出现"QObject::killTimer: timers cannot be stopped from another thread"的运行错误。
相关链接:
http://labs.qt.nokia.com/blogs/2010/06/17/youre-doing-it-wrong/
http://labs.qt.nokia.com/blogs/2006/12/04/threading-without-the-headache/
http://labs.qt.nokia.com/blogs/2007/07/05/qthreads-no-longer-abstract/
http://gitorious.org/qthreadhowto/qthreadhowto/trees/master
http://blog.exys.org/entries/2010/QThread_affinity.html
http://thesmithfam.org/blog/2010/02/07/talking-to-qt-threads/
QThread实例代表一个线程,我们可以重新实现QThread::run(),要新建一个线程,我们应该先继承QThread并重新实现run()函数。
需要注意的是:
1.必须在创建QThread对象之前创建 QApplication (或QCoreApplication)对象。
2. QCoreApplication::exec() 必须只能从主线程调用,不能从QThread线程调用。
class MyThread : public QThread
{
Q_OBJECT
public:
MyThread(QObject *parent = NULL);
~MyThread();
protected:
voidrun();
};
void MyThread::run()
{
for( int count = 0; count < 20; count++ )
{
sleep( 1 );
}
qDebug( "finish!");
}
我们可以在另外的函数这样调用:
MyThread thread;
thread.start();
thread.wait(); //必须要加的语句,等待thread结束。
当然我们先开的线程可能是要运行很久,会卡住主线程,使用QeventLoop就可以轻松解决此类问题:
MyThread thread;
thread.start();
connect(&thread,SIGNAL(finished ()),&eventLoop,SLOT(quit()));
thread.wait(1);
eventLoop.exec();
接下来我们看QThread的其他属性和函数:
Qthread的优先级属性:Priority指示系统如何调度线程。
Constant |
Value |
Description |
QThread::IdlePriority |
0 |
scheduled only when no other threads are running. |
QThread::LowestPriority |
1 |
scheduled less often than LowPriority. |
QThread::LowPriority |
2 |
scheduled less often than NormalPriority. |
QThread::NormalPriority |
3 |
the default priority of the operating system. |
QThread::HighPriority |
4 |
scheduled more often than NormalPriority. |
QThread::HighestPriority |
5 |
scheduled more often than HighPriority. |
QThread::TimeCriticalPriority |
6 |
scheduled as often as possible. |
7 |
use the same priority as the creating thread. This is the default. |
0到6的优先级跟windows线程相对应的,就多了一个InheritPriority。
非静态成员函数:
void |
exit ( int returnCode = 0 ) |
该函数告诉线程退出事件循环和返回代码returnCode ;通常返回0意味着成功,非0意味着错误。
需要注意的是,该函数不像C语言的库函数exit一样返回到调用者。除非再次调用QThread::exec(),否则不会再执行任何事件循环。如果QThread::exec()的事件循环没有运行,接着的QThread::exec()将会立即返回。
这两个函数看名字就知道意思:
bool QThread::isFinished ()const
如果线程结束,则返回true,否则返回false。
bool QThread::isRunning ()const
如果线程还在运行则返回true,否则返回false
void QThread::setPriority ( Priority priority )
该函数设置了正在运行的线程的优先级。如果线程没有运行,函数什么也不做,直接返回。跟踪源码发现setPriority 的源码部分如下:
if(!d->running)
{
qWarning("QThread::setPriority:Cannot set priority, thread is not running");
return;
}
需要注意的是:参数不能是InheritPriorty。
case InheritPriority:
default:
qWarning("QThread::setPriority:Argument cannot be InheritPriority");
return;
如果是InheritPriorty,函数也只输出一个警告便返回了。
优先级参数的影响依赖于操作系统的调度策略。特别是,在不支持线程优先级的系统(如Linux)优先级参数将被忽略。
void QThread::setStackSize ( uint stackSize )
该函数设置线程堆栈的最大值。
需要注意的是:
1.必须在线程启动之前设置
2. 虽然参数是uint类型,传入负数的话会自动转换。
3.大部分的操作系统都设置了线程堆栈的最大最小值限制。如果超出了限制线程可能运行失败。
验证了一下,当我传-1进去,stackSize值变成了4294967295。Start函数里调用beginthreadex创建新线程。Beginthreadex里调用CreateThread这是大部分用过windows多线程的程序员都了解的。创建失败则输出警告并返回。
d->handle = (Qt::HANDLE)_beginthreadex(NULL, d->stackSize, QThreadPrivate::start,
this, CREATE_SUSPENDED, &(d->id));
if(!d->handle) {
qErrnoWarning(errno, "QThread::start: Failed to create thread");
d->running = false;
d->finished = true;
return;
}
uint QThread::stackSize () const
该函数返回线程堆栈最大值,如果调用setStackSize()设置最大值的话,返回的就是设置的值,否则返回0;
bool QThread::wait ( unsigned long time =ULONG_MAX )
该函数阻塞线程直到满足以下条件之一;
1. 线程已经执行结束。如果线程结束,该函数返回true。如果线程没被启动也会返回true。
2. Time毫秒时间过去了。如果time 为 ULONG_MAX,该函数永远不会超时。如果超时,函数返回false。
槽函数:
void QThread::quit ()
函数退出事件循环,返回代码为0.与调用exit(0).一样的效果。如果线程没有事件循环则什么也不做。
void QThread::start ( Priority priority =InheritPriority ) [slot]
通过调用开始run(),执行线程。如果线程已经在运行,则什么也不做。
看一下源码:
Start()调用:
d->handle =(Qt::HANDLE) _beginthreadex(NULL, d->stackSize, QThreadPrivate::start, this,CREATE_SUSPENDED, &(d->id));
创建线程。
unsigned int __stdcall QT_ENSURE_STACK_ALIGNED_FOR_SSEQThreadPrivate::start(void *arg)
{
QThread *thr = reinterpret_cast<QThread*>(arg);
QThreadData *data = QThreadData::get2(thr);
。。。。。
。。。。。。 //中间省略,删掉了
emitthr->started(); //发送started信号。
QThread::setTerminationEnabled(true);
thr->run(); //运行我们重新实现的代码。
finish(arg); //该函数发送finished();信号。
return 0;
}
void QThread::terminate ()
终止线程的运行。线程可能不会理解被终止,这依赖于操作系统的调用策略。在terminate()之后调用QThread::wait()同步终止。
终止线程之后,所有等待线程结束的线程都会被唤醒。
void QThread::terminate()
{
Q_D(QThread);
QMutexLocker locker(&d->mutex);
if(!d->running)
return;
if(!d->terminationEnabled) {
d->terminatePending = true;
return;
}
TerminateThread(d->handle, 0);
d->terminated = true;
QThreadPrivate::finish(this, false); //finish函数发送了finished()信号。从而会唤醒所有等待的线程。
}
除非真的有必要,不然不要轻易调用该函数。
QThread * QThread::currentThread ()
函数返回当前正在执行的线程。
Qt::HANDLE QThread::currentThreadId ()
注意:
返回当前运行的线程的句柄。该函数返回的句柄只能内部使用,不能在其他任何程序的代码中使用。
在windows系统,该返回值是当前线程的伪句柄。不能用作数值比较,函数返回Win32 函数 getCurrentThreadId()返回的DWORD值。而不是 Win32 函数 getCurrentThread()返回的HANDLE值。
int QThread::idealThreadCount ()
函数返回系统可以运行的理想的线程数。
int QThread::idealThreadCount()
{
SYSTEM_INFO sysinfo;
GetSystemInfo(&sysinfo);
returnsysinfo.dwNumberOfProcessors; //返回系统中的处理器的数目。
}
void QThread::yieldCurrentThread ()
放弃当前线程转到另外可执行的线程,有系统决定转到哪个线程。
void QThread::sleep ( unsigned long secs )
void QThread::msleep ( unsigned long msecs )
void QThread::usleep ( unsigned long usecs )
这三个函数是让线程进入休眠状态。它们内部都是调用了windows API Sleep。
void QThread::setTerminationEnabled ( bool enabled =true )
设置是否可以终止当前线程。
转自:http://blog.csdn.net/hai200501019/article/details/9535069