线程 线程的创建 线程的回收 线程的取消

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

线程

  什么是线程?可以这么理解,一个进程相当于一个人,多进程就是多个人,多进程运行就是多个人在做事情,而线程就是一个人可以干的事情,一个人是可以同时干很多事情的,比如一边听歌一边写作业一边自言自语,那么一个进程里面可以有多个线程在执行。

  linux下面最小分配资源的单位是进程,而线程是最小的执行单位。内核执行程序的时候看的是PCB,PCB有几个那么就执行几个程序。原来进程中只有一个PCB,创建了线程之后,内核区就会多一个PCB出来。创建几次线程就多几个PCB出来。当进程创建了线程之后,这个进程就变成了主线程,也是一个线程。

  简单来想,就是本来一个大脑控制身体,现在突然有个多个大脑一起控制身体。可以把进程里面的多个线程运行的时候看成是多个进程在运行,在执行第一个PCB时候用的也是这副身体,也就是系统分配的这块地址,然后进行这个大脑想做的操作,执行第二个PCB的时候也用的是这副身体,进行第二个大脑想做的操作。那么这里需要注意的是,这个身体也不是可以完全共享的,除了栈空间基本上都是可以共享的(包括文件描述符表,这个也是共享的)。它们共享一个身体,PID也就只有一个,那它们内部是怎么区分的呢?每个线程都有自己的线程id,线程号,系统是通过线程号来区分不同线程的(注意,线程号是给系统内核看的,线程id是给程序员看的)。那它们有没有执行的先后顺序呢?其实这个和进程一样,谁先抢到cpu时间片谁先执行。
linux下的线程本质上仍是进程,轻量级的进程,因为对于内核来说,只认PCB。

线程的创建

  需要注意的是:创建线程的函数和进程不一样,创建进程的函数是系统提供的API接口而创建线程的函数是c语言的库函数,但是二者最后都是利用了底层函数clone。比如说我们c语言用的printf函数是c语言的库函数,是对底层的print函数封装之后的样子,可以让我们更方便的使用。print的再底层就是对硬件显示器等的操作了。

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

  pthread_t thread:是一个传出参数,也就是给程序员看的线程id.是创建出来的子线程的id。
成功返回0;失败返回错误号,pthread_t
thread这个参数的值就不确定了。

  const pthread_attr_t *attr:设置创建出来的这个子线程的属性(这个下面等等会介绍,设计线程的分离属性等等)

  void (start_routine)(void ):传的是函数指针,指向线程的主函数,对于线程的主函数可以理解成主线程的main函数,该函数运行结束,线程就推出了。

  void *arg:给这个线程主函数传递参数,这是一个万能指针,线程主函数可以将这个万能指针改变类型,以想要的形式解引用出来。

线程的回收

  注意:线程中谨慎使用exit函数,使用exit函数会使这个进程退出,进程退出来被回收资源后,线程也就没办法再执行了。就好比多个大脑操作一个身体,一个大脑的行为直接把这个身体给杀死了,其他大脑也就无力回天了。(return也相当于调用了exit())
取而代之的是pthread_exit函数,这个函数只会使当前线程退出,如果主线程调用pthread_exit函数也不会使整个进程退出,不影响其他线程的执行,如果进程中只有一个线程,那么会使整个进程结束退出。

线程退出:

  void pthread_exit(void *retval);

  这个函数的参数是和线程的回收函数结合起来用的,传的参数可以被回收它的函数所接收。通常传NULL。

线程回收:

  int pthread_join(pthread_t thread, void **retval);

  阻塞等待线程退出,获得线程的退出状态,并且回收资源。注意:这里获得线程的退出状态是二级指针,上面那个函数传的参不能建立在栈区,否则线程退出后栈被回收,这里将指向一块不确定值的地方。

  传入需要回收的线程id(不是线程号)。成功返回0,失败返回错误号。

设置线程的分离属性

  当线程被设置为分离状态时,就不需要线程回收函数来回收他了,当它运行结束后,资源自行释放,也就是自动回收,如果这时候再用回收函数来回收他,回收函数不再阻塞,而是直接返回错误号。(当线程自己回收自己的时候也会返回错误号,不是返回0)

  线程分离属性设置:

  int pthread_detach(pthread_t thread);

  函数成功返回0失败返回错误号。

  可以在线程创建的时候就为其设置好属性,这个函数用在创建线程成功之后设置的。

  在创建时候设置分离属性有些许复杂,创建的时候第二个参数需要传一个const pthread_attr_t *attr
  这个的本质是一个结构体,里面有很多可以设置的东西只不过都被隐藏起来了,我们没法具体设置,不过可以通过提供的几个函数来完成对这个pthread_attr_t类型的设置。这里介绍如何设置分离属性。

  先创建出一个这个类型的变量:

  pthread_attr_t attr;

  再对这个变量进行初始化:

  int pthread_attr_init (pthread_attr_t* attr);

  设置线程为分离属性:

  int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

  第一个参数也就是传要改的变量,上面创建的attr(注意这里是指针的方式接收),第二个参数传要改成的属性,这里有两个宏值:

      PTHREAD_CREATE_DETACHED(分离)

      PTHREAD_CREATE_JOINABLE(非分离)

然后就可以在创建的时候把attr这个变量传入pthread_create函数了,这时候创建的线程就是分离属性的线程。其实这几部的操作就是为pthread_create函数的第二个参数做准备工作。

需要注意的是,这个attr是占用资源的,需要释放线程属性资源。

  int pthread_attr_destroy(pthread_attr_t *attr);

线程的取消

  取消线程相当于给指定线程发送终止信号,让这个线程停止运行,但这个有一定的延时性,为什么这么说呢?因为需要线程在收到这个东西之后不会立刻终止而是到达一个取消点才会停止。

  int pthread_cancel(pthread_t thread);

  函数成功返回0,失败返回错误号。

  这个取消点可以手动设置,利用下面函数。

  void pthread_testcancel(void);

  取消点的作用是为了检查这个线程是否被取消,如果没有取消点这个线程就会一直执行到结束位置。

  上面说到可以手动设置,那么也可以不设置。为什么这么说呢?简单来说就是当线程有函数进入到了内核,在内核就会检查这个线程是否被取消。通常一些系统调用都会进入内核,比如printf函数 read函数、write函数等等,一进入到内核就会检查这个线程是否被取消。手动设置的函数的本质也是进入内核,如果不进入内核线程就算收到pthread_cancel发出的信号也不会取消。

作者:不爱学习的王小二 原文地址:https://juejin.cn/post/7127554356215283743

%s 个评论

要回复文章请先登录注册