多线程
线程概念
大条件:Linux
中:
因为
Linux
环境下线程以进程的
PCB
模拟实现线程,所以
Linux
中
PCB
是线程。因此
Linux
线程也叫轻量级进程。
进程就是线程组。以前所描述的进程其实是单线程进程。
因为
Linux
线程是
PCB
,因此线程是
CPU
调度的基本单位。
因为进程是线程组,程序运行起来,资源是分配给整个线程组的,因此进程是资源分配的基本单位。
多进程可以并行多任务 / 多线程也可以并行多任务,何者更优?
多进程与多线程优缺点对比
- 优点:
一个进程中的线程共用同一个虚拟地址空间,因此:
- 线程间通信更加方便。
- 线程的创建 / 销毁成本更低。
- 线程间切换调度成本更低。
- 线程的执行粒度更细致。
- 缺点:
线程缺乏访问控制:系统调用(如exit()
),异常针对的是整个进程,健壮性低。
多进程 / 多线程进行多任务处理的优势体现与细节:
CPU
密集型程序
IO
密集型程序
vfork()
创建一个子进程共用同一块虚拟地址空间,避免出现调用栈混乱,因此子进程运行完毕或程序替换后父进程才开始运行。
多线程
PCB
使用同一块虚拟地址空间,如何实现同步运行而不会出现调用栈混乱?
为每个线程在虚拟地址空间中单独分配一块空间,每个线程都会有一些独立的信息(栈、寄存器、
errno
、信号屏蔽字、调度优先级)
线程之间共享的数据:代码段、数据段、文件描述符表、每种信号的处理方式、用户id、组id、当前工作目录路径。
线程控制
操作系统并没有为用户提供直接创建线程的系统调用接口,因此提供者封装了一套线程库,实现线程控制。
因此我们用户创建的线程是一个用户态线程,在内核态中对应了一个轻量级进程,实现程序的调度运行。
线程创建
pthread_create()接口
需要包含的头文件
,因为是库函数,因此链接时需要加上
-pthread
或
-lpthread
选项来链接线程库。
int pthread_create(
pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine) (void *),
void *arg
);
thread
:输出型参数,用于获取新创建的线程id
。
attr
:设置线程属性,通常置NULL
。
start_routine
:线程的入口函数,是一个函数指针。
arg
:作为实参,通过线程入口函数传递给线程。
- 返回值:
成功返回0
。
失败返回errno
,它的大小>0
。
tid
:线程地址空间的首地址。
task_struct
中的
pid
:LWD。
task_struct
中的
tgid
:PID=主线程的pid。
ps -L
:查看轻量级进程(线程)信息。其中
LWP
查看的就是
task_struct
中的
pid
。
线程终止
-
pthread_exit
退出调用线程
在线程入口函数中return,pthread_exit退出调用线程,主线程退出,进程并不会退出,线程退出也会成为僵尸线程(但是普通线程退出不出效果),线程地址空间无法被回收再利用,造成资源泄露。
-
pthread_cancel
取消一个指定的线程
线程等待
线程等待的前提:线程能够被等待。
因为一个线程运行起来默认有一个属性
joinable
,处于这个属性的线程退出后,必须被等待,否则线程资源无法完全释放。因为线程退出后,为了保存返回值,不会自动回收线程的资源,所以成为僵尸线程(无法直观体现)。
所以选择使用
pthread_join
接口实现线程等待,获取指定线程的返回值,允许系统回收资源。
接口
pthread_join
:
int pthread_join(pthread_t thread, void **retval);
thread
:要等待的线程id。
retval
:输出型参数,用于获取退出线程的返回值。
【注】如果一个线程是被取消的,则返回值是一个宏
PTHREAD_CANCELED
,它的值是
-1
。
线程分离
将线程的一个属性从
joinable
设置为
detach
属性,处于
detach
属性的线程退出后,
资源直接自动被回收。这类线程不能被等待。
线程的分离对于一个线程来说,无论何处任意线程调用都可以。
pthread_detach
接口:
int pthread_detach(pthread_t thread);
用法:通常如果用户对线程的返回值不关心,则在创建线程之后直接分离线程或者在线程入口函数中第一时间分离自己。