从pthread 到QThread
该文出自:http://www.civilnet.cn/bbs/topicno/78430
使用线程技术进行应用编程的意义在gemfield的文章《从进程到线程》中已经介绍过了,我们就直奔主题:Linux上的线程开发以及Qt对线程的封装(Linux平台部分)。Linux上的线程API使用的是pthread库,我们来粗略认识下pthread。
要在程序中使用pthread线程,究竟会用到哪几部分的功能呢? 1、创建线程是必须的吧,pthread_create(); 2、设置线程的属性也是需要的吧 3、终止线程的功能也是必须的吧 4、回收线程所占用的资源也是需要的吧 还有? 5、同步是个大的命题!;
上面的第5点要在gemfield的下一篇文章中介绍了。
那么Qt的线程类QThread对外提供了什么接口和功能呢?好像也差不多: 1、start() ,开始一个线程; 2、quit()、terminate(),结束一个线程; 3、setPriority ( Priority priority ),设置调度优先级; 4、setStackSize (uint stackSize),设置线程栈的大小; 5、wait(unsigned long time = ULONG_MAX ),阻塞一个线程直到下面的条件满足了(类似于POSIX的pthread_join()):
第5点中的wait()意义如下:
*和此线程关联的另外一个线程运行完毕了; *参数指定的毫秒数过去了之后. 如果时间是LONG_MAX (默认), 那相当于这个条件就作废了,只能看第一个条件了。
依上面的内容看,Qt的接口和pthread的接口看起来差不多嘛…
事实是怎样的呢?Qt的库在Linux平台上的部分究竟是怎么封装pthread的呢?这一切,先从熟悉pthread的使用开始。
我们把gemfield的《从进程到线程》一文中的示例代码clone.c拿过来,重名命为gemfield-thread.c(符合gemfield本文的主旋律 ),如下:
*******************gemfield-thread.c********************************* #include <stdio.h> #include <pthread.h> int gemfield =0; void civilnet_cn(){ printf(“gemfield do clone***\n”); scanf(“%d”,gemfield); }
int main(int argc,char **argv) { pthread_t tid; int ret = pthread_create(&tid,NULL,civilnet_cn,NULL); printf(“gemfield do clone…\n”); scanf(“%d”,gemfield); } **************************************************** 编译: gcc gemfield-thread.c -lpthread -o gemfield-thread 运行: ./gemfield-thread&
第一部分、线程的创建 int pthread_create(pthread_t * thread, const pthread_attr_t * attr,void * (*start_routine)(void *), void *arg);
从gemfield-thread.c中可以看出,创建一个线程用的是pthread_create函数。这个函数的系统调用过程在gemfield的《从进程到线程》中有所描述。
与fork()调用创建一个进程的方法不同,pthread_create()创建的线程并不具备与主线程(即调用pthread_create()的线程)同样的执行序列,而是使其运行civilnet_cn函数 (pthread_create的第三个参数)。pthread_create将创建的线程的id赋给tid变量(第一个参数,该id同样可以通过pthread_self()函数来获得),pthread_create()的返回值表示线程创建是否成功(0为成功,否则不成功,返回非零值并设置errno)。
注意,第二个参数的实参为NULL,表明我们没有给这个新的线程设置任何的属性。那么,究竟可以设置什么样的属性呢,如果我想要的话? 第二部分、线程属性:pthread_attr_t结构体以及作用于其上的pthread_attr_*函数族调用 虽然在gemfield-thread.c中,我们创建新线程时第二个参数pthread_attr_t * attr为NULL,但你完全可以充分使用这个参数。
在pthread中,我们不是直接访问pthread_attr_t结构体,而是通过pthread_attr_*函数族调用来访问,常见的函数有: ******************************************************* pthread_attr_t attr;//线程属性结构体
//初始化pthread_attr_t结构体 pthread_attr_init(&attr);
//设置分离状态,参数默认是PTHREAD_CREATE_JOINABLE, //一旦设置为PTHREAD_CREATE_DETACHED,则该线程不能被使用pthread_join(), //并且线程结束后系统自行释放它所占用的资源 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
//参数有两种值可供选择:PTHREAD_EXPLICIT_SCHED和PTHREAD_INHERIT_SCHED, //前者表示新线程使用显式指定调度策略和调度参数(即attr中的值), //后者表示继承调用者线程的值 。参数默认为PTHREAD_EXPLICIT_SCHED。 pthread_attr_setinheritsched(&attr, PTHREAD_INHERIT_SCHED);
//调度策略,取值有: SCHED_OTHER(正常、非实时), //SCHED_RR(实时、轮转法)和 SCHED_FIFO(实时、先入先出)三种, //默认为SCHED_OTHER,后两种调度策略仅对超级用户有效 。 //运行时可以用过 pthread_setschedparam()来改变。 pthread_attr_setschedpolicy(&attr, sched_policy) //调度策略,取值同上; pthread_attr_getschedpolicy(&attr, &sched_policy)
//改变调度策略,参数为一个struct sched_param结构, //目前仅有一个sched_priority整型变量表示线程的运行优先级。 //这个参数仅当调度策略为实时(即SCHED_RR 或SCHED_FIFO)时才有效 , //并可以在运行时通过pthread_setschedparam()函数来改变,默认为0。 pthread_attr_setschedparam(&attr, &sp)
//设置线程栈的大小 pthread_attr_setstacksize(&attr, stackSize)
//销毁pthread_attr_t结构体,在pthread_create()执行后调用这个函数 pthread_attr_destroy(&attr) *******************************************************
第三部分、线程的取消和终止
通过pthread_create()创建的线程(用civilnet_cn代表)开始了自己的运行,那civilnet_cn什么时候结束呢?
1、线程civilnet_cn的代码执行完毕的时候;
这种情况下相当于人类的寿终正寝,非常完美,但还是要分2种情况:civilnet_cn线程创建的时候有没有使用pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)来设置分离属性:
a、如果没有设置的话,终止的线程所占用的资源并不会随着线程的终止而得到释放,于是,主线程还要为寿终正寝的线程办理丧事;不过正如进程之间可以用wait()系统调用来同步终止并释 放资源一样,线程之间也有类似机制,那就是pthread_join()。使用pthread_join()来释放civilnet_cn所占用的资源;
pthread_join(pthread_t thread, void **status)调用的本意是:阻塞主线程(发出这个调用的线程)直到civilnet_cn结束,然后分离civilnet_cn,并将civilnet_cn的返回值放在status 里。
需要注意的是一个线程仅允许唯一的一个线程使用pthread_join()等待它的终止,并且被等待的线程应该处于可join状态,即非DETACHED状态。
如果主线程执行了pthread_detach(civilnet_cn),则civilnet_cn线程将处于DETACHED状态,这使得civilnet_cn线程在结束运行时自行释放所占用的内存资源,同时也无法由pthread_join() 同步,pthread_detach(civilnet_cn)执行之后,对civilnet_cn请求pthread_join()将返回错误。
一个可join的线程所占用的内存仅当有线程对其执行了pthread_join()后才会释放,因此为了避免内存泄漏,所有线程的终止,要么已设为DETACHED,要么就需要使用pthread_join()来回收 。
b、如果设置了的话,就表明civilnet_cn线程一出生就和主线程分离了,那就啥都不用管,由系统回收civilnet_cn所占用的资源;
2、主线程向civilnet_cn发出pthread_cancel()调用的时候;
a、发送终止信号给civilnet_cn线程,如果成功则返回0,否则为非0值。发送成功并不意味着civilnet_cn会终止 (还要考虑civilnet_cn的状态以及取消点的位置)。
b、该调用向civilnet_cn线程发出cancel信号,但如何处理cancel信号则由civilnet_cn线程自己决定(由自己的cancel状态决定,参考下文) ,或者忽略(参考pthread_setcancelstate)、或者立即终止(pthread_setcanceltype)、或者继续运行至cancel-point(取消点,默认)。
c、默认情况下(pthread_create()创建线程的缺省状态),civilnet_cn线程接收到cancel信号后 ,是继续运行至取消点 ,也就是说设置一个cancel状态,civilnet_cn线程继续运行至取消点的时候才会退出。
d、那什么是取消点呢?
根据POSIX标准,pthread_join()、pthread_testcancel()、 pthread_cond_wait()、pthread_cond_timedwait()、sem_wait()、sigwait()等函数以及 read()、write()等会引起阻塞的系统调用都是取消点 ,而其他pthread函数都不会引起取消动作。但是pthread_cancel的手册页声称,由于LinuxThread库与C库结合得不好,因而目前C库函数都不是取消点 ;但cancel信号会使线程从阻塞的系统调用中退出,并置EINTR错误码 ,因此可以在需要作为 cancel-point的系统调用前后调用pthread_testcancel(),从而达到POSIX标准所要求的目标。比如:
pthread_testcancel(); retcode = read(fd, buffer, length); pthread_testcancel();
e、通过pthread_setcancelstate(int state, int *oldstate) 可以设置civilnet_cn对cancel信号的反应 ,state有两种值:PTHREAD_CANCEL_ENABLE(缺省)和 PTHREAD_CANCEL_DISABLE, 分别表示收到信号后设为CANCLED状态和忽略CANCEL信号继续运行;
f、通过pthread_setcanceltype(int type, int *oldtype) 设置civilnet_cn线程取消动作的执行时机,type由两种取值:PTHREAD_CANCEL_DEFFERED(默认)和 PTHREAD_CANCEL_ASYCHRONOUS, 仅当cancel状态为Enable时有效 ,分别表示收到cancel信号后继续运行至下一个取消点再退出和 立即执行取消动作(退出) ;
g、通过pthread_testcancel(void) 检查civilnet_cn线程是否处于canceld状态,如果是,则进行取消动作 ,否则直接返回;
3、主线程向civilnet_cn发出pthread_exit()调用的时候;该函数用于退出当前线程,退出之前将调用pthread_cleanup_push,该函数在线程的上层函数中是被隐式调用的;
4、主线程向civilnet_cn发出pthread_kill()调用,并且参数是SIGKILL信号时;调用的时候;哈哈,这一点是骗你的;因为通过pthread_kill调用发送SIGKILL信号时,会导致整个进程终止,不管你是想发给哪个线程;因为SIGKILL这个信号被设计出来的使命就是终止整个进程;
5、发生异常或者某些硬件特性导致的线程终止;这个,我也无能为力。
第五部分:线程终止时的清理
如何保证civilnet_cn线程终止时能顺利的释放掉自己所占用的资源,特别是锁资源,是一个必须考虑解决的问题。
最经常出现的情形是mutex的使用:civilnet_cn线程为了访问临界资源而为其加上锁,但在访问过程中civilnet_cn线程被外界取消,如果线程处于响应取消状态,且采用异步方式响应,或者在打开独占锁以前的运行路径上存在取消点,则该临界资源将永远处于锁定状态得不到释放。外界取消操作是不可预见的,因此的确需要一个机制来简化用于资源释放的编程。
在POSIX线程API中提供了一个pthread_cleanup_push()/pthread_cleanup_pop()函数对用于自动释放资源–从pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序段中的终止动作(包括调用pthread_exit()和取消点终止)都将执行pthread_cleanup_push()所指定的清理函数。函数定义如下:
void pthread_cleanup_push(void (*routine) (void *), void *arg) void pthread_cleanup_pop(int execute)
pthread_cleanup_push()/pthread_cleanup_pop()采用先入后出的栈结构管理,void routine(void *arg)函数在调用pthread_cleanup_push()时压入清理函数栈,多次对 pthread_cleanup_push()的调用将在清理函数栈中形成一个函数链,在执行该函数链时按照压栈的相反顺序弹出。execute参数表示执行到pthread_cleanup_pop()时是否在弹出清理函数的同时执行该函数,为0表示不执行,非0为执行;这个参数并不影响异常终止时清理函数的执行。
pthread_cleanup_push()/pthread_cleanup_pop()是以宏方式实现的,这是pthread.h中的宏定义:
#define pthread_cleanup_push(routine,arg) \ { struct _pthread_cleanup_buffer _buffer; \ _pthread_cleanup_push (&_buffer, (routine), (arg)); #define pthread_cleanup_pop(execute) \ _pthread_cleanup_pop (&_buffer, (execute)); }
可见,pthread_cleanup_push()带有一个”{“,而pthread_cleanup_pop()带有一个”}”,因此这两个函数必须成对出现,且必须位于程序的同一级别的代码段中才能通过编译。在下面的例子里 ,当线程在”do some work”中终止时,将主动调用pthread_mutex_unlock(mut),以完成解锁动作。
pthread_cleanup_push(pthread_mutex_unlock, (void *) &mut);
pthread_mutex_lock(&mut); /* do some work */ pthread_mutex_unlock(&mut); pthread_cleanup_pop(0);
必须要注意的是,如果线程处于PTHREAD_CANCEL_ASYNCHRONOUS状态,上述代码段就有可能出错,因为CANCEL事件有可能在pthread_cleanup_push()和pthread_mutex_lock()之间发生,或者在pthread_mutex_unlock()和pthread_cleanup_pop()之间发生,从而导致清理函数unlock一个并没有加锁的mutex变量,造成错误。因此,在使用清理函数的时候,都应该暂时设置成 PTHREAD_CANCEL_DEFERRED模式。为此,POSIX的Linux实现中还提供了一对不保证可移植的pthread_cleanup_push_defer_np()/pthread_cleanup_pop_defer_np()扩展函数,功能与以下代码段相当:
{ int oldtype; pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldtype); pthread_cleanup_push(routine, arg); … pthread_cleanup_pop(execute); pthread_setcanceltype(oldtype, NULL); }
第六部分:线程中特有的线程存储
线程中特有的线程存储——Thread Specific Data(TSD) 。线程存储是什么意思呢?又有什么用?从gemfield的《从进程到线程》中我们知道,在多线程程序中,全局变量可以被所有线程访问。这在线程间要共享数据时是有意义和灵活的,但是,如果每个线程希望能单独拥有自己的全局变量,那么就需要使用线程存储了。表面上看起来这是一个全局变量,所有线程都可以使用它,而它的值在每一个线程中又是单独存储的。这就是线程存储的意义。
下面是线程存储的具体用法:
1、创建一个类型为 pthread_key_t 类型的变量;
2、调用 pthread_key_create(pthread_key_t *key, void (*destr_function) (void *)) 来创建该变量;该函数有两个参数,第一个参数就是上面声明的 pthread_key_t 变量,第二个参数 是一个清理函数,用来在线程释放该线程存储的时候被调用。该函数指针可以设成 NULL ,这样系统将调用默认的清理函数;
不论哪个线程调用pthread_key_create(),所创建的key都是所有线程可访问的(参见下面的第6步),但各个线程可根据自己的需要往key中填入不同的值 ,这就相当于提供了一个同名而不同值的全局变量。在LinuxThreads的实现中,TSD池用一个结构数组表示:
static struct pthread_key_struct pthread_keys[PTHREAD_KEYS_MAX] = { { 0, NULL } };
数据存放与一个32×32的稀疏矩阵中 。同样,访问的时候也由key值经过类似计算得到数据所在位置索引,再取出其中内容返回。(说的更为简单一点就是把一个一维数组模拟成为二维数组,通过除法得到属于哪一层,通过取余运算得到属于该层的第几个元素 。 3、当线程中需要存储特殊值的时候,可以调用int pthread_setspecific(pthread_key_t key, const void *value); 该函数有两个参数,第一个为前面声明的 pthread_key_t 变量,第二个 为 void* 变量,这样你可以存储任何类型的值;
4、如果需要取出所存储的值,调用 void *pthread_getspecific(pthread_key_t key); 该函数的参数为前面提到的 pthread_key_t 变量,该函数返回 void * 类型的值。
5、注销一个TSD采用如下API:int pthread_key_delete(pthread_key_t key);这个函数并不检查当前是否有线程正使用该TSD,也不会调用清理函数(destr_function) ,而只是将TSD释放以供下一次调用 pthread_key_create()使用 。 6、在上面的第2步中有这么一句话:“不论哪个线程调用pthread_key_create(),所创建的key都是所有线程可访问的(参见下面的第6步)”,这也就是说,对于一个多线程程序来说,只需要有一个这样的key就可以了。那究竟由哪个线程来创建这个key呢?因为线程的并发性,我们无法预期哪个线程会首先执行;保险的做法就是每一个线程中都有一个pthread_key_create()调用;那怎样保证一旦key被创建后,其他的线程就别再创建呢?这就是pthread_once()的价值了:
int pthread_once(pthread_once_t *once_control, void (*init_routine) (void))
本函数使用初值为PTHREAD_ONCE_INIT的once_control变量保证init_routine()函数在本进程执行序列中仅执行一次。而这个init_routine()在gemfield这里的上下文中就是要调用 pthread_key_create()的函数了:-)
gemfield的本文到了这里,就简单的说完了pthread的用法了;虽然还没有说到pthread_mutex_* 、pthread_cond_*、 pthread_rwlock_*的API(它们分别是互斥、条件变量、读写锁,留在gemfield下面的文章中介绍),但我们还是要了解下QThread是怎样封装pthread API的(Linux平台上)。 QThread类的结构如下(省略了信号和其他一些东西) ************************************************** class Q_CORE_EXPORT QThread : public QObject { public: static Qt::HANDLE currentThreadId(); static QThread *currentThread(); static int idealThreadCount(); explicit QThread(QObject *parent = 0); enum Priority { …. }; void setPriority(Priority priority); void setStackSize(uint stackSize); void exit(int retcode = 0); QAbstractEventDispatcher *eventDispatcher() const; void setEventDispatcher(QAbstractEventDispatcher *eventDispatcher); public Q_SLOTS: void start(Priority = InheritPriority); void terminate(); void quit(); public: bool wait(unsigned long time = ULONG_MAX); static void sleep(unsigned long); static void msleep(unsigned long); static void usleep(unsigned long); protected: virtual void run(); int exec(); static void setTerminationEnabled(bool enabled = true); private: Q_OBJECT Q_DECLARE_PRIVATE(QThread) static void initialize(); static void cleanup(); friend class QCoreApplication; friend class QThreadData; }; ********************************************************** 看起来QThread类的内部并没有什么数据成员?那它怎么实现的线程机制?别急,有一个叫作Q_DECLARE_PRIVATE的宏如下: #define Q_DECLARE_PRIVATE(Class) \ inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(qGetPtrHelper(d_ptr)); } \ inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(qGetPtrHelper(d_ptr)); } \ friend class Class##Private;
以及: template <typename T> static inline T *qGetPtrHelper(T *ptr) { return ptr; } template <typename Wrapper> static inline typename Wrapper::pointer qGetPtrHelper(const Wrapper &p) { return p.data(); }
(等等!上面的qGetPtrHelper()函数是神马情况?只是把参数原封不动的返回,要它有什么用?) (别急,ptr 可能是一个智能指针 (比如:QScopedPointer) ,如果是这样的话,是不能给reinterpret_cast作转换用的,这样d_func()必须通过data()这个成员函数来访问智能指针内部的指针: qGetPtrHelper所作的就是提供了一个统一的处理方法。)
所以QThread就成了 class Q_CORE_EXPORT QThread : public QObject { …… private: inline QThreadPrivate* d_func() { return reinterpret_cast<QThreadPrivate *>(d_ptr); } friend class QThreadPrivate; } 其中,d_ptr的类型是QObjectData*,指向的却是从QObjectData继承而来的QObjectPrivate*类型的成员(QObjectData是基类,d_ptr相当于是指向派生类对象的基类指针); ************************************************************* QScopedPointer<QObjectData> d_ptr; QObject::QObject(QObjectPrivate &dd, QObject *parent): d_ptr(&dd) //在下文中,QObjectPrivate &dd这个参数传过来的实参正是QThreadPrivate; *************************************************************** 因为QObjectPrivate是个好东西,来看看它的成员: QObjectPrivate::QObjectPrivate(int version):threadData(0), connectionLists(0), senders(0), currentSender(0), currentChildBeingDeleted(0) { q_ptr = 0; parent = 0; //不是父对象,通过setParent()置位 isWidget = false;//不是一个widget blockSig = false;//是否阻塞信号 wasDeleted = false;// double-delete catcher isDeletingChildren = false;//通过deleteChildren()置位 sendChildEvents = true;//是否把ChildInsert和ChildRemove事件发给父对象 receiveChildEvents = true; postedEvents = 0; extraData = 0; connectedSignals[0] = connectedSignals[1] = 0; metaObject = 0; isWindow = false; }
QObjectData的定义如下: ************************************************************ class QObjectData { public: virtual ~QObjectData() = 0; QObject *q_ptr; QObject *parent; QObjectList children;
uint isWidget : 1; uint blockSig : 1; uint wasDeleted : 1; uint isDeletingChildren : 1; uint sendChildEvents : 1; uint receiveChildEvents : 1; uint isWindow : 1; //for QWindow uint unused : 25; int postedEvents; QMetaObject *metaObject; // assert dynamic }; ************************************************************ QThreadPrivate的定义如下: *********************************************************** class QThreadPrivate : public QObjectPrivate { Q_DECLARE_PUBLIC(QThread) public: QThreadPrivate(QThreadData *d = 0); ~QThreadPrivate(); mutable QMutex mutex; bool running; bool finished; bool terminated; bool isInFinish; //when in QThreadPrivate::finish bool exited; int returnCode; uint stackSize; QThread::Priority priority; static QThread *threadForId(int id); pthread_t thread_id; QWaitCondition thread_done; static void *start(void *arg); static void finish(void *); QThreadData *data; static void createEventDispatcher(QThreadData *data); }; *********************************************** QThreadData的定义如下: ********************************************** class QThreadData { QAtomicInt _ref; public: QThreadData(int initialRefCount = 1); ~QThreadData(); static QThreadData *current(); static QThreadData *get2(QThread *thread) { Q_ASSERT_X(thread != 0, “QThread”, “internal error”); return thread->d_func()->data; } void ref(); void deref(); QThread *thread; Qt::HANDLE threadId; bool quitNow; int loopLevel; QAbstractEventDispatcher *eventDispatcher; QStack<QEventLoop *> eventLoops; QPostEventList postEventList; bool canWait; QVector<void *> tls; bool isAdopted; }; ********************************************** 了解了QThread的类结构体系后,我们来看看具体的实施吧: 使用Qt的QThread,一般的步骤是: 1、从QThread派生一个类MyThread; 2、在MyThread中重新实施run这个虚函数; 3、实例化一个对象gemfieldThread; 4、调用gemfieldThread->start()开始这个线程; 5、要结束gemfieldThread这个线程,两种方法:
第一、退出线程的事件循环: gemfieldThread->quit(); gemfieldThread->wait(); gemfieldThread->deleteLater(); 第二、终止线程的运行: gemfieldThread->terminate(); gemfieldThread->wait(); gemfieldThread->deleteLater();
一段代码演示如下: ******************************************** class MyThread : public QThread { public: void run(); };
void MyThread::run() { QTcpSocket socket; // connect QTcpSocket’s signals somewhere meaningful … socket.connectToHost(hostName, portNumber); exec(); } *********************************** 或者访问:http://www.civilnet.cn/bbs/topicno/6487 来了解更多Qt线程的用法
下面,就让gemfield带领你来看看上面的过程是怎么封装pthread的:
第一步、构造一个QThread对象: QThread::QThread(QObject *parent): QObject(*(new QThreadPrivate), parent) { Q_D(QThread); d->data->thread = this; } 其中,Q_D宏如下: #define Q_D(Class) Class##Private * const d = d_func() 也即: QThread::QThread(QObject *parent): QObject(*(new QThreadPrivate), parent) { //gemfield是(QThreadPrivate* gemfield = new QThreadPrivate) QThreadPrivate * const d = reinterpret_cast<QThreadPrivate *>(gemfield); d->data->thread = this;//QThreadData的成员QThread*指向自己 }
第二步、start调用究竟发生了什么?start()是怎样调用到我们在MyThread重新实施的run()函数呢?
在QThread模块中,start()在Linux平台上的实施是在qthread_unix.cpp中完成的,代码如下: ***************************************** void QThread::start(Priority priority)//优先级 { Q_D(QThread); QMutexLocker locker(&d->mutex);
if (d->isInFinish) d->thread_done.wait(locker.mutex());
if (d->running) return;
d->running = true; d->finished = false; d->terminated = false; d->returnCode = 0; d->exited = false;
pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
d->priority = priority;
switch (priority) { case InheritPriority: { pthread_attr_setinheritsched(&attr, PTHREAD_INHERIT_SCHED); break; }
default: { int sched_policy; if (pthread_attr_getschedpolicy(&attr, &sched_policy) != 0) { // failed to get the scheduling policy, don’t bother // setting the priority qWarning(“QThread::start: Cannot determine default scheduler policy”); break; }
int prio; if (!calculateUnixPriority(priority, &sched_policy, &prio)) { // failed to get the scheduling parameters, don’t // bother setting the priority qWarning(“QThread::start: Cannot determine scheduler priority range”); break; }
sched_param sp; sp.sched_priority = prio;
if (pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED) != 0 || pthread_attr_setschedpolicy(&attr, sched_policy) != 0 || pthread_attr_setschedparam(&attr, &sp) != 0) { // could not set scheduling hints, fallback to inheriting them // we’ll try again from inside the thread pthread_attr_setinheritsched(&attr, PTHREAD_INHERIT_SCHED); d->priority = Priority(priority | ThreadPriorityResetFlag); } break; } }
if (d->stackSize > 0) { int code = pthread_attr_setstacksize(&attr, d->stackSize);
if (code) { qWarning(“QThread::start: Thread stack size error: %s”, qPrintable(qt_error_string(code))); d->running = false; d->finished = false; return; } } int code =pthread_create(&d->thread_id, &attr, QThreadPrivate::start, this); if (code == EPERM) { // caller does not have permission to set the scheduling // parameters/policy pthread_attr_setinheritsched(&attr, PTHREAD_INHERIT_SCHED); code =pthread_create(&d->thread_id, &attr, QThreadPrivate::start, this); } pthread_attr_destroy(&attr); if (code) { qWarning(“QThread::start: Thread creation error: %s”, qPrintable(qt_error_string(code))); d->running = false; d->finished = false; d->thread_id = 0; } } ************************************************ 在上面的代码中,我们终于看到了gemfield在上文中讲解过的pthread API了。其中:
int code =pthread_create(&d->thread_id, &attr, QThreadPrivate::start, this);
将QThreadPrivate::start作为线程的实体,我们再看看QThreadPrivate::start是怎么实施的(this指针参考:http://civilnet.cn/bbs/topicno/71261,下面的代码用到了): ********************************************** void *QThreadPrivate::start(void *arg) { pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); pthread_cleanup_push(QThreadPrivate::finish, arg); //参数arg是this QThread *thr = reinterpret_cast<QThread *>(arg); QThreadData *data = QThreadData::get2(thr);
// do we need to reset the thread priority? if (int(thr->d_func()->priority) & ThreadPriorityResetFlag) { thr->setPriority(QThread::Priority(thr->d_func()->priority & ~ThreadPriorityResetFlag)); }
data->threadId = (Qt::HANDLE)pthread_self(); set_thread_data(data);
data->ref(); { QMutexLocker locker(&thr->d_func()->mutex); data->quitNow = thr->d_func()->exited; }
if (data->eventDispatcher) // custom event dispatcher set? data->eventDispatcher->startingUp(); else createEventDispatcher(data);
emit thr->started(); pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); pthread_testcancel(); //执行重新实施的run虚函数 thr->run();
pthread_cleanup_pop(1);
return 0; } **********************************************
第三部分:gemfieldThread->quit()是怎么实施的呢?
首先明白quit是干什么的?当这个线程有自己的事件循环的话,那么这些事件循环全部退出;而如果没有事件循环的话,则什么也不干; quit()调用的是exit(),而exit()的实施如下:
void QThread::exit(int returnCode) { Q_D(QThread); QMutexLocker locker(&d->mutex); d->exited = true; d->returnCode = returnCode; d->data->quitNow = true; for (int i = 0; i < d->data->eventLoops.size(); ++i) { QEventLoop *eventLoop = d->data->eventLoops.at(i); eventLoop->exit(returnCode); } }
第四部分:gemfieldThread->terminate()是怎么运行的?
这个函数终止一个线程的执行,但并不是立即被执行的,还要取决于操作系统的调度策略;看过了gemfield本文的开头部分,你这里就会更明白;在调用terminate() 之后使用 QThread::wait() 来同步终止. ************************************************************ void QThread::terminate() { Q_D(QThread); QMutexLocker locker(&d->mutex);
if (!d->thread_id) return;
int code = pthread_cancel(d->thread_id); if (code) { qWarning(“QThread::start: Thread termination error: %s”, qPrintable(qt_error_string((code)))); } else { d->terminated = true; } } ************************************************************* 哈哈,又看见我们熟悉的pthread的API了。
第五部分:上面屡次提到的gemfieldThread->wait()又是怎样同步的呢?
wait()函数的作用参考本文的开头部分。 ********************************************************* bool QThread::wait(unsigned long time) { Q_D(QThread); QMutexLocker locker(&d->mutex);
if (d->thread_id == pthread_self()) { qWarning(“QThread::wait: Thread tried to wait on itself”); return false; }
if (d->finished || !d->running) return true;
while (d->running) { if (!d->thread_done.wait(locker.mutex(), time))//参考QWaitCondition::wait() return false; } return true; } *******************************************************
最后:说说QThreadData,在Qt的事件循环中用的更多,但因为底层实现和本文有关,就放在这里说说了:
QThreadData *data = QThreadData::current();
而QThreadData::current()是怎么实现的呢?
QThreadData *QThreadData::current() { QThreadData *data = get_thread_data(); if (!data) { data = new QThreadData; QT_TRY { set_thread_data(data); data->thread = new QAdoptedThread(data); } QT_CATCH(…) { clear_thread_data(); data->deref(); data = 0; QT_RETHROW; } data->deref(); data->isAdopted = true; data->threadId = (Qt::HANDLE)pthread_self(); if (!QCoreApplicationPrivate::theMainThread) QCoreApplicationPrivate::theMainThread = data->thread; } return data; } 也就是当前程序中尽管有多个线程或者事件循环,但实际上只维护了1个QThreadData结构体,但这个结构体在不同的线程中对应着自己的值(有自己的索引),而这个QThreadData靠的是set_thread_data(data)来初始化,而靠get_thread_data()来获得;
再来看看这两者的实现:
static QThreadData *get_thread_data() { #ifdef HAVE_TLS return currentThreadData; #else pthread_once(¤t_thread_data_once, create_current_thread_data_key); return reinterpret_cast<QThreadData *>(pthread_getspecific(current_thread_data_key)); #endif }
static void set_thread_data(QThreadData *data) { #ifdef HAVE_TLS currentThreadData = data; #endif pthread_once(¤t_thread_data_once, create_current_thread_data_key); pthread_setspecific(current_thread_data_key, data); } //多熟悉的pthread调用呀!
从pthread 到QThread的更多相关文章
- QThread中的互斥、读写锁、信号量、条件变量
该文出自:http://www.civilnet.cn/bbs/browse.php?topicno=78431 在gemfield的<从pthread到QThread>一文中我们了解了线 ...
- 【Qt开发】QThread中的互斥、读写锁、信号量、条件变量
在gemfield的<从pthread到QThread>一文中我们了解了线程的基本使用,但是有一大部分的内容当时说要放到这片文章里讨论,那就是线程的同步问题.关于这个问题,gemfield ...
- QWord2vec:word2vec移植版+GUI
序 Word2Vec原生是不支持Windows的,索性就用Qt移植了一下. 大概做了下面几件事. ①替换LinuxAPI的pthread为QThread. ②取消了posix_memalign(),内 ...
- Qt线程(1) moveToThread
若在Qt准备使用线程类一般有两种方式(1) 采用WorkObject配合QThread进行使用 (2)继承QThread, 重载run()函数即可. 注:采用Qt::Concurrent之类的不在本文 ...
- 一种基于Qt的可伸缩的全异步C/S架构server实现(二) 网络传输
二.网络传输模块 模块相应代码命名空间 (namespace ZPNetwork) 模块相应代码存储目录 (\ZoomPipeline_FuncSvr\network) 2.1 模块结构 ...
- (十七)线程,connect的第五个参数
采用多线程,将需要处理的后台数据放入子线程,为了能够跨线程调用,一种方法是使用类似线程锁对线程进行保护,另外一种方法使用Qt的信号槽机制.Qt的信号槽机制采用connect函数进行连接,connect ...
- Qt线程(2) QThread中使用WorkObject
一般继承QThread的WorkThread都会在重载的run()中创建临时的WorkObject,这样能确定这个WorkObject在该thread中使用 那如果这个WorkObject是个Sing ...
- Qt之QThread随记
这是一篇随记,排版什么的就没有那么好了:) 首先要知道,一个线程在资源分配完之后是以某段代码为起点开始执行的,例如STL内的std::thread,POSIX下的pthread等,都是以函数加其参数之 ...
- 使用C++11的thread取代QThread
因为在做的工程项目里使用了Qt,而实际上不涉及到屏幕显示,工程代码里使用了QThread,且没有使用Qt核心的信号与槽,为了以后移植准备使用更加通用的C++11 stl中的thread取代QThrea ...
随机推荐
- CF1042C Array Product(贪心,模拟)
题目描述 You are given an array aa consisting of nn integers. You can perform the following operations w ...
- ABAP术语-Business Object
Business Object 原文:http://www.cnblogs.com/qiangsheng/archive/2008/01/07/1028364.html Represents a ce ...
- springboot properties文件中的数据通过@Value()形式注入
首先在resources目录下新建一个properties文件,如下图 在photoPath.properties中写入内容,key=value的形式,如下图 在你需要引用properties的类头部 ...
- Java四舍五入时保留指定小数位数
方式一: double f = 3.1516; BigDecimal b = new BigDecimal(f); , BigDecimal.ROUND_HALF_UP).doubleValue(); ...
- 配置Github秘钥
Git安装完成后,需要手动配置ssh密钥 配置github的ssh密钥: (1)打开Git Bash查看电脑上是否已经存在SSH密钥: 输入 cd ~/.ssh 若如上图显示无法找到该文件则要创建新的 ...
- JS日期去杠,日期转换String转Date
1.巧妙使用split()和join()替换字符串var str = '2014-05-05';var newstr = str.split('-').join("");split ...
- PHPPCRE正则解析
一.前言 前面的博客里,有对字符集的解析.这里就不是字符集的事儿了,在PHP中很多函数的处理默认是unicode中的UTF-8编码格式.那么废话不多说,直接开始正题. 二.PHP函数mb_split解 ...
- 静态栈抽象数据类型stack实现
#include<stdio.h> #include<stdbool.h> #include<stdlib.h> #define MAX_STACK_SIZE 10 ...
- 002---time & datetime
time & datetime 时间模块 分类 时间戳 时间字符串 时间元祖 定义 UTC:格林威治时间,世界标准时间,中国(UTC + 8) 时间戳:1970-01-01 0:0:0 开始按 ...
- SPLIT(文字列の分割)
概要 SPLIT命令は特定の文字で値を分割する命令だ.タブ区切りや.カンマ区切り等のファイルからデータを取得し値を各項目に振り分けたい時に使用する事が多いだろう.また.XMLファイル等を使用してインタ ...