Linux中的线程

2019-07-14 10:03发布

什么是线程

在之前进程的介绍中,一个进程拥有一个PCB,一个虚拟地址空间,一个页表,这样的角度理解进程是比较狭义的。在一个进程中,可以存在一个或多个线程,每一个线程就是一个执行流,那么该如何理解线程呢? CPU调度执行流是通过PCB调度的,当一个进程中存在多个线程(执行流)时,也就是说在一个进程中,存在多个PCB可以被CPU调度。而每一个PCB即代表一个线程,同一个进程下的线程共享同一份虚拟地址空间,同一份页表。线程在进程内部运行,本质是在进程的地址空间内运行

多线程下的进程图示

FKvIeJ.png
  • 线程仅代表一个PCB。
  • 进程代表一个或多个PCB,一个虚拟地址空间,一个页表,以及内存中的数据&映射关系等一整套东西。

线程与进程的区别

  • 资源的区别
    • 创建一个进程,操作系统会给该进程创建至少一个PCB,一个虚拟地址空间,一个页表并将进程所需要的数据加载到内存并维护他们之间的映射关系。
    • 创建一个线程,操作系统仅仅会创建一个PCB,线程在进程的地址空间内运行,线程仅仅是进程中的一个执行流。
  • 进程是资源分配的基本单位;线程是调度的基本单位
  • 线程共享进程数据,但线程也拥有自己的一份数据:
    • 线程ID
    • 一组寄存器:线程的上下文
    • 运行时栈
    • errno
    • 信号屏蔽字
    • 调度优先级

线程的优点

  • 创建线程时更加轻量化。
  • 与进程的切换相比,线程之间的切换要做的工作要少很多,CPU调度线程的成本要小很多。
    • 进程切换时,要切换进程的上下文、虚拟地址空间、页表、映射关系等。
    • 线程切换时,仅需要切换线程的上下文。
  • 线程占用的资源比进程小很多。
  • 线程可以充分利用多处理器的可并行数。
  • 进程与进程中的资源共享较为方便。
  • 在等待慢速I/O操作结束的同时,程序可以执行其他计算任务。

线程的缺点

  • 性能丢失
    增加了额外的同步&调度开销。
  • 健壮性降低
    当某一个线程出现异常时就会造成整个进程的异常。
  • 缺乏访问控制
    线程运行在进程的虚拟地址空间中,所以同一个进程中的多个线程往往是共享该进程中的全局资源的。
  • 编程难度提高
    多线程的程序较单线程更加难以调试。

线程控制

POSIX线程库

  • 与线程有关的函数构成了一个完整的系列,绝大多数函数名都已pthread_开始
  • 要使用这些接口,需要引入头文件
  • 链接这些线程函数库时要使用编译器命令"-lpthread"选项

创建线程

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(start_routine)(void*), void *arg);
  • 参数
    • *thread:创建出的线程的特定标识码,该参数需要用户自己定义。
    • *attr:设置线程属性,attr为NULL时表示使用默认的线程属性。
    • *start_routine:函数指针,表明线程启动后要执行的函数。
    • *arg:传递给线程启动函数的参数。
  • 返回值
    • 成功返回0;失败返回错误码

线程终止

  • 从线程函数return
  • 调用pthread_exit接口
  • 一个线程调用pthread_cancel接口终止同一进程中的另一个线程
int pthread_cancel(pthread_t thread); int pthread_exit(void *retval);
  • 参数
    • thread:线程的特定标识码
    • *retval:线程的退出码,使用指针指向线程退出码的原因是因为在线程栈上开辟的空间,不能保证它的有效性。
  • 返回值
    • 成功返回0;错误返回错误码

线程等待

如果主线程不等待其他线程,也会造成类似于僵尸进程的问题。但如果不关心线程的返回值,也可以使用线程分离的方式让操作系统自动回收线程。 int pthread_join(pthread_t thread, void **retval); //若主线程运行到pthread_join开始等待线程,但该线程迟迟不退出,则主线程此时会被阻塞,知道等到目标线程退出之后继续向下执行 参数
  • thread:线程的特定标识码
  • **retval:返回指向线程执行的回调函数的返回指针的指针。

线程分离

  • 当一个新线程被创建时,默认情况下是joinable的,线程退出之后,需要对它进行pthread_join(等待)操作,否则无法释放线程资源。
  • 当创建一个线程,不需要得知该线程的退出码时,此时对该线程进行pthread_join自然会造成一种不必要的负担,此时就可以使用线程分离,让该线程执行完成之后,操作系统自动回收线程资源。
int pthread_detach(pthread_t thread); //可以是线程组内对其他线程进行分离,也可以是分离自身 //线程进行分离之后,并不是说该线程跟主线程毫无关系,若主线程在其他线程完成之前退出,则其他线程也会退出
  • 参数
    • thread:线程的特定标识码
  • 返回值
    • 成功返回0;失败返回错误码

获取自身线程的特定标识码

pthread_t pthread_self(void); 返回值:当前线程的特定标识码

线程ID与进程ID

struct task_struct { ... pid_t pid; //线程ID pid_t tgid; //线程组ID ... struct task_struct *group_leader; ... struct list_head thread_group; ... }; 用户态 系统调用 内核进程描述符中对应的结构 线程ID pid_t gettid(void); pid_t pid 进程ID pid_t getpid(void); pid_t tgid

线程组

站在Linux操作系统的角度,并不会认识什么是线程,Linux操作系统将一切都当作一个进程来调度。操作系统在底层进行调度的时候,是按照pid来调度的。同一个进程下的所有线程的tgid是相同的,这表明他们属于同一个线程组,在每一个线程组中都会有一个线程的pid与tgid是相同的,这表明该线程是该线程组的组长,即为主线程。