Qt系列文章034-从pthread 到QThread

1 前言

  今天想记录一下 Linux上的线程开发以及Qt对线程的封装(Linux平台部分)。 所以内容会偏向理论部分,而今天要记录的这篇文章,也是我从网上看了以后,觉得很有必要记录一番,非常的有理论意义和深入线程的探索挖掘!好了,直接进入枯燥的理论主题吧!

公众号:Qt实战,各种开源作品、经验整理、项目实战技巧,专注Qt/C++软件开发,视频监控、物联网、工业控制、嵌入式软件、国产化系统应用软件开发。

公众号:Qt入门和进阶,专门介绍Qt/C++相关知识点学习,帮助Qt开发者更好的深入学习Qt。多位Qt元婴期大神,一步步带你从入门到进阶,走上财务自由之路。

官方店:https://shop114595942.taobao.com//

2 正文

  首先Linux上的线程API使用的是pthread库,所有我们来粗略认识下 pthread

  要在程序中使用pthread线程,究竟会用到哪几部分的功能呢?

  1. 创建线程是必须的吧,pthread_create();
  2. 设置线程的属性也是需要的吧
  3. 终止线程的功能也是必须的吧
  4. 回收线程所占用的资源也是需要的吧 还有?
  5. 同步是个大的命题!(我前面的章节已经深入讲解过QThread的同步了,这边大同小异)

  那么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的使用开始。

  首先新建一个 fl_pthread.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); 
}

  编译C文件:

gcc fl_pthread.c -lpthread -o fl_pthread

  运行:

 ./fl_pthread &

3.1 pthread线程的创建

int pthread_create(pthread_t * thread, const pthread_attr_t * attr,
				  void * (*start_routine)(void *), void *arg);

  从 fl_pthread.c 中可以看出,创建一个线程用的是 pthread_create 函数。

  与 fork() 调用创建一个进程的方法不同,pthread_create() 创建的线程并不具备与主线程(即调用pthread_create() 的线程)同样的执行序列,而是使其运行 civilnet_cn 函数 ( pthread_create 的第三个参数)。pthread_create 将创建的线程的 id 赋给 tid 变量(第一个参数,该 id 同样可以通过 pthread_self() 函数来获得),pthread_create() 的返回值表示线程创建是否成功(0为成功,否则不成功,返回非零值并设置errno)。

  注意,第二个参数的实参为 NULL,表明我们没有给这个新的线程设置任何的属性。那么,究竟可以设置什么样的属性呢,如果我想要的话?

3.2 pthread 线程属性

  pthread_attr_t 结构体以及作用于其上的 pthread_attr_* 函数族调用 ,虽然在 fl_pthread.c 中,我们创建新线程时第二个参数pthread_attr_t * attrNULL,但你完全可以充分使用这个参数。

  在 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) 

3.3 线程的取消和终止

  通过 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. 发生异常或者某些硬件特性导致的线程终止!这个,我也无能为力。

3.4 线程终止时的清理

  如何保证 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); 
}

3.5 线程中特有的线程存储

  线程中特有的线程存储—— Thread Specific Data(TSD) 。线程存储是什么意思呢?又有什么用?在多线程程序中,全局变量可以被所有线程访问。这在线程间要共享数据时是有意义和灵活的,但是,如果每个线程希望能单独拥有自己的全局变量,那么就需要使用线程存储了。表面上看起来这是一个全局变量,所有线程都可以使用它,而它的值在每一个线程中又是单独存储的。这就是线程存储的意义。

  下面是线程存储的具体用法:

  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_INITonce_control 变量保证 init_routine() 函数在本进程执行序列中仅执行一次。而这个 init_routine() 在 这里的上下文中就是要调用 pthread_key_create() 的函数了:-)

3.6 QThread 封装解刨

  到了这里,就简单的说完了pthread的用法了,虽然还没有说到pthread_mutex_* 、pthread_cond_*、 pthread_rwlock_* 的API(它们分别是互斥、条件变量、读写锁,留在下面的文章中介绍),但我们还是要了解下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(); 
} 

  更多用法看我前面的博客!Qt系列文章之二十九(基于QThread的掷骰子线程实例讲解)

QThread 如何封装pthread

  下面,就让博主带领你来看看上面的过程是怎么封装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; 
	 } 
}

  在上面的代码中,我们终于看到了在前面讲解过的pthread API了。其中:

int code = pthread_create(&d->thread_id, &attr, QThreadPrivate::start, this);

  将 QThreadPrivate::start 作为线程的实体,我们再看看 QThreadPrivate::start 是怎么实施的:

 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调用呀!

次节到此结束,下一节,将详细解刨 QThread的同步类的构成。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值