从pthread 到QThread

转自:https://www.cnblogs.com/lfsblack/p/4614541.html

使用线程技术进行应用编程的意义在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(&current_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(&current_thread_data_once, create_current_thread_data_key); pthread_setspecific(current_thread_data_key, data); } //多熟悉的pthread调用呀!

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值