QT实战——多线程学习笔记(一)

1、创建个线程,注意需要先引用

#include <QThread>
void *Thread(void *arg)

其中arg为要传给线程的变量,函数的返回值应该表示线程函数运行的结果:成功还是失败。注意:函数名是自定义的。

在QT中线程函数不能直接调用!!!线程函数是一个不能直接调用、需要实现的虚拟函数。

QThread::run();

通常需要自己继承QThread,并实现run()函数,然后由系统调用

2、QT中使用POSIX多线程库的基本API函数

线程创建函数pthread_create()的原型声明如下:

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

参数pid是一个指针,指向创建成功后的线程ID, pthread_t就是unsigned long int;attr是指向线程属性结构pthread_attr_t的指针,使用NULL则表明是默认属性;start_routine指向线程函数的地址,线程函数就是线程创建后要执行的函数;arg指向传给线程函数的参数;如果成功,该返回值为0。

#include <QCoreApplication>
#include <unistd.h> //for sleep

void *thfunc(void *arg)//线程函数
{
  printf("in thfunc\n");
  return (void *)0;
}

int main( int argc, char *argv[])
{
  QCoreApplication a(argc, argv);
  pthread_t tidp;
  int ret;

  ret = pthread_creat(&tidp, NULL, thfunc, NULL); //创建线程
  if(ret)
  {
    printf("pthread_creat failed:%d\n", ret);
    return -1;
  }

  sleep(1);//main线程挂起1s,为了让子线程有机会执行
  printf("in main:thread is created\n");
  
  return a.exec();
}

线程创建等待函数pthread_join()声明如下:

int pthread_join(pthread_t pid, void **value_ptr);

参数pid是所等待线程的ID号;value_ptr通常可以设置为NULL,若不是NULL,那么该函数回复制一份线程退出值到一个内存区域,并让*value_ptr指向该内存区域 ,该函数的一个重要功能是获得子线程的返回值,如果成功执行,那么返回值为0,否则返回错误代码。

3、POSIX 标准规定线程的多个属性

线程主要属性包括:分离状态(Detached State)、调度策略和参数(Scheduling Policy and Parameters)、作用域(Scope)、堆栈尺寸(Stack Size)、堆栈地址(Stack Address)、优先级(Priority)等。在Linux中定义成一个联合体pthread_attr_t

基本流程 pthread_attr_init()初始化线程属性、pthread_attr_creat()创建一个线程、等待线程运行完毕,pthread_attr_destroy()进行销毁,从而释放相关资源。

其中int pthread_attr_init()的原型声明:pthread_attr_init(pthread_attr_t *attr);

       int pthread_attr_destroy()的原型声明:pthread_attr_destroy(pthread_attr_t *attr);

(1)分离状态属性

对于可连接的线程,主线程可以调用pthread_join()函数等待子线程结束,而对于分离线程,在主线程中可以调用pthread_exit(),如果在main线程中调用了pthread_exit(),那么此时终止的只是main线程,而进程的资源会为由main线程创建的其他线程保持打开的状态,直到其他线程都终止。值得注意的是非main线程(其他子线程)中调用pthread_exit(),则不会有这样的效果,只会推出当前的子线程。

(2)堆栈尺寸

获取线程堆栈尺寸属性的函数是pthread_attr_getstacksize(),该函数的原型声明如下:

int pthread_attr_getstacksize(pthread_attr_t *attr, size_t *stacksize);

参数attr指向属性结构体,stacksize用于获得堆栈尺寸(单位是字节),指向size_t类型的变量。如果调用成功返回值为0,否则返回错误代码。

(3)调度策略

SCHED_OTER表示分时调度策略(也可称轮转策略),是一种非实时调度策略,系统会为每个线程分配一段运行时间。

SCHED_FIFO表示先来先服务调度策略,是一种实时调度策略,支持优先级抢占,可以算是一种实时调度策略。

SHCED_RR表示时间片轮转(轮询)调度策略,但支持优先级抢占,因此也是一种实时调度策略。

4、线程结束

在Linux下,线程的结束通常由一下原因所致:

(1)在线程函数中调用pthread_exit()函数

(2)线程所属的进程结束了,比如进程调用了exit()

(3)线程函数执行结束后(return)返回了

(4)线程被同一进程中的其他线程通知结束或取消

与window下的线程退出函数ExitThread()不同,函数pthread_exit()不会导致C++对象被析构。第(2)种方法不推荐,因为线程函数如果有C++对象,则C++对象不会被销毁。第(3)中方法推荐使用,是最安全的方式。最后一种方法通常用于其他线程要求目标线程结束运行的情况,比如目标线程发送取消信号。第一、三种属于线程自己主动终止,第二、四种属于被动结束。

一般情况,对于可连接的线程,它终止后所占用的资源并不会随着线程的终止而归还给系统,仍为线程的其他进程所持有,可以调用pthread_join()函数来同步并释放资源。

1、线程主动结束

一般就是在线程中使用return语句或调用pthread_exit()函数。

pthread_exit()的原型声明如下:void pthread_exit(void *retval);

其中参数retval是线程退出时返回给主线程的值,线程函数的返回类型是void*。在main线程中调用“pthread_exit(NULL);"的时候将结束main线程,但进程并不会立即退出。

2、线程被动结束

一种方法是在同进程的另外一个线程中通过函数pthread_kill()发送信号给想要的结束的线程,目标线程收到信号后退出;另一种方式是在同进程的其他线程中通过函数pthread_cancel()来取消目标线程的执行。

注意pthread_kill()不是杀死(kill)线程,而是向线程发信号,因此线程之间交流信息可以用这个函数,另外要注意,接受信号的线程必须先用sigaction()函数注册该信号的处理函数。

pthread_kill()的原型声明如下:int pthread_kill(pthread_t threadId, int signal);

参数threadId是接受信号的线程ID;signal是信号,通常是一个大于0的值,如果等于0,就是用来探测线程是否存在的,若为EINVAL则表示信号不合法。

向指定ID的线程发送signal信号,如果线程代码内不进行处理,则按照信号默认的行为影响整个进程,如果一个线程发送了SIGQUIT,但线程没有实现signal的处理函数,则整个进程退出。所以,如果int sig 的参数不是0,那么一定要清楚到底要干什么,最好要实现线程的信号处理函数,否则影响整个进程。

pthread_cancel()是用来取消某个线程的执行,但是要注意就算发送成功了也不一定意味着线程会停止运行。

pthread_cancel()的原型声明:int pthread_cancel(pthread_t thread);

参数thread表示要被取消线程(目标线程)的线程ID。如果发送取消请求成功,则函数返回0,否则返回错误代码。

发送该信号后,系统不会马上关闭被取消的线程,只有让内核去检测是否需要取消当前线程时,才会真正的结束线程。如果取消线程成功,就将自动返回常数PTHREAD_CANCELED(这个值为-1),可以通过pthread_join()函数获得这个退出值。也就是发送这个信号后,用pthread_join(pthread,&ret),获取ret值,再通过if判断ret是否等于PTHREAD_CANCELED

5、线程退出时的清理

对于POSIX线程提供了pthread_cleanup_push()pthread_cleanup_pop(),用于线程退出时执行一些清理操作,前者用于把一个函数压入清理函数栈,后者用于弹出栈顶的清理函数,并根据参数来决定是否执行清理函数。多次调用pthread_cleanup_push()将把当前在栈顶的清理函数往下压,弹出清理函数时在栈顶的清理函数将先被弹出。

pthread_cleanup_push()函数的原型声明:pthread_cleanup_push(void (*routine)(void *),  void *arg); 参数routine是一个函数指针,arg是该函数的参数。

用pthread_cleanup_push()压栈的清理函数在下面3种情况下会执行:

(1)线程主动结束时,比如return或调用pthread_exit()函数

(2)调用函数pthread_cleanup_pop(),且它的参数为非0时

(3)线程被其他线程取消时,也就是有其他线程对该线程调用了pthread_cancel()函数

pthread_cleanup_pop()函数的原型声明:pthread_cleanup_pop(int execute);

参数execute用来决定在弹出栈顶清理函数的同时是否执行清理函数,取0时表示不执行清理函数,取非0时表示执行清理函数。

注意pthread_cleanup_pop()与pthread_cleanup_push()必须成对地出现在同一个函数中!!,否则就是语法错误。

void mycleanfunc(void *arg)
{
  pthread_cleanup_push(clean_func,...) //压栈一个清理函数 clean_func()
  pthread_mutex_lock(&mutex);          //上锁
  //调用某个阻塞函数,比如套接字的accept(),该函数等待客户连接
  sock = accept(...........);
  
  pthread_mutex_unlock(&mutex);        //解锁
  pthread_cleanup_pop(0);              //弹出清理函数,但不执行,因为参数为0
  return NULL;
}

accept被其他线程取消后线程1退出,则会自动调用clean_func()函数,在这个函数种释放资源。如果accept没有被取消,那么线程继续执行,当执行至 pthread_mutex_unlock(&mutex);时,线程自己正确释放资源,再执行至pthread_cleanup_pop(0);时把前面压栈的清理函数clean_func()弹出栈,不执行(因为参数为0)。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值