线程概念
Linux下没有真正的线程,因为Linux下的线程是用进程pcb模拟的(也就是说Linux下pcb实际上是一个线程),所以Linux下的线程也被叫做轻量级进程。既然Linux下pcb成了线程,那么进程变成了线程组。
线程是进程的一条执行流,Linux下线程是以进程的pcb模拟的,所以才说Linux下的线程是轻量级进程,因此Linux下的线程是cpu调度的基本单位。
Linux下的进程成了线程组,进程id==线程组id,所以才说Linux下的进程是线程组,资源分配给整个线程组的,所以进程是资源分配的基本单位,并且进程中的线程共享大部分进程的资源。
线程共享
- 文件描述符表
- 用户id、组id
- 信号处理方式
- 工作路径(当前工作目录)
- 共享虚拟地址空间——共享地址段和代码段
线程独有(相对独有——因为数据还是在虚拟地址空间)
- 栈区
- 上下文数据
- 线程id
- errno
- 信号屏蔽字
- 一组寄存器
线程的优缺点
优点:
linux下的线程共用进程的虚拟地址空间
- 线程的创建/销毁成本更低——不用每次都创建虚拟地址空间
- 线程的调度切换成本更低
- 线程间的通信更加方便
- 线程的执行力度更加细致
缺点:
- 缺乏访问控制——线程安全
- 有些系统调用和程序异常时针对整个进程产生影响的
- 多个线程对临界(公共)资源进行操作有可能会造成数据混乱
线程控制
线程创建
pthred_create
int pthred_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg)
thread //获取线程id(用户态线程id) 无符号长整形
attr //设置线程属性,通常置空
start_routine //线程的入口函数(线程所运行的代码)
arg //线程入口函数的参数
返回值 //成功返回0,失败返回错误码
通过参数返回一个用户态的线程id,这个线程id是线程地址空间在进程虚拟地址空间的首地址,用户态线程的操作都是围绕这个用户态的线程id来操作的。
每一个线程都是一个task_struck,也就意味着每个线程都有一个pid,但是ps命令只能显示一个。task_struct中不仅有pid还有一个tgid(线程组id==线程组领导者的pid),那么ps看到的pid实际上是这个tgid(线程组id),因此我们说linux下的进程称为的线程组。
查看线程信息可以使用 ps-L命令, LWP这一项显示的就是线程pcb中的pid。
- tid 线程id
- pid 进程id
- tgid 线程组id(主线程的pid)
线程终止
不能再main函数中return,不能调用exit函数,因为这两个都是退出进程的,进程推出了,所有的线程都得退出。
pthread_exit
void pthread_exit(void *value_ptr) //退出调用线程
//value_ptr:value_ptr不要指向一个局部变量。
//返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)
pthread_exit(NULL);
pthread_cancel
void pthread_cancel(pthread_t thread) //退出指定线程
//thread:线程ID
//返回值:成功返回0;失败返回错误码
pthread_cancel(tid);
线程等待
- 获得其他普通线程的退出返回值,避免产生僵尸线程。
- 获得退出线程的返回值,并且允许操作系统回收线程资源。
线程等待的前提: 只有线程处于joinable状态(线程默认属性),这个线程才能被等待。
pthrad_join
int pthrad_join(pthread_t thread, void **value_ptr)
//thread 线程id
//value_ptr 用于获取一个线程退出的返回值
//阻塞性函数,如果线程没有退出就一直等待
线程分离
默认情况下,新创建的线程是joinable状态的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。 如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程
资源。
pthread_deatch
int pthread_detach(pthread_t thread) //设置线程分离属性(deatch)
//thread 指定要分离的线程id
//分离指定的线程,被分离的线程退出时自动回收资源
//处于detach状态的线程退出后自动回收资源,不会保存返回值,所以不需要被等待
线程安全
因为线程是cpu调度的基本单位,因此多个线程可能会同时争夺对临界资源的操作,多个线程因为临界资源的争抢写入操作会导致程序逻辑的混乱/数据的二义性;同时,线程本身就是因为通信方便以及成本低而广为使用,这样就无法避免大量临界资源的争抢操作,这时候就必须要考虑如何保证线程安全。
线程间的同步与互斥
互斥: 任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源。
同步: 让数据的访问更加具有时序的可控性,保证对临界资源的时序可控性。
互斥锁 — 互斥
大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程无法获得这种变量。但有时候,很多变量都需要在线程间通过数据的共享,完成线程之间的交互,这样的变量称为共享变量。而多个线程并发的操作共享变量,会带来一些问题。因此,我们需要一扇门,来保证只有一个线程进入临界区,Linux下,为我们提供了一种方式,称为互斥锁。
初始化互斥锁变量
pthread_mutex_t mutex; //互斥锁变量
//01、
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//02、
pthread_mutex_init(); //初始化
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
//mutex 互斥量
//attr 属性
加锁
pthread_mutex_lock //阻塞加锁,获取不到一直等待
int pthread_mutex_lock(pthread_mutex_t *mutex);
//成功返回0, 失败返回错误信号
pthread_mutex_trylock(); //非阻塞加锁,获取不到直接退出
pthread_mutex_timedlock(); //限时阻塞加锁
解锁
pthread_mutex_unlock(); //解锁
//在任何有可能退出的地方都必须解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
//成功返回0, 失败返回错误信号
释放互斥锁
pthread_mutex_destroy //释放
int pthread_mutex_destroy(pthread_mutex_t *mutex);
条件变量 — 同步
满足操作条件,才可以操作,不满足需要等待,而条件满足就需要对其他线程修改条件,并通知一下等待的线程。
初始化条件变量
pthread_cond_t cond //条件变量
//01、
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
//02、
pthread_cond_init //初始化
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
//cond 条件变量
//attr 条件变量的属性,通常置空
等待
pthread_cond_wait(); //等待
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex)
//cond 要在这个条件变量上等待
//mutex:互斥量
///这个函数需要在挂起之前对互斥锁进行解锁操作
//这个时候解锁操作和挂起操作必须是一个原子操作,否则不安全
//条件变量中的解锁和休眠操作必须是原子操作,并且在被唤醒后会将互斥锁的值修改为0
//条件变量的判断必须是一个循环,否则如果有多个线程被同时唤醒,则对临界资源的访问不安全
pthread_cond_timedwait(); //限时等待
通知
pthread_cond_signal //通知(通知等待在条件变量上的线程/进程)
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
销毁条件变量
pthread_cond_destroy(); //销毁
int pthread_cond_destroy(pthread_cond_t *cond);
死锁
一个程序一直获取不到锁,因此一直处于卡死状态就称为死锁。
死锁产生的四个必要条件
- 互斥条件
- 只有一个能够获得锁
- 不可剥夺条件
- 使用时别人不能释放
- 请求与保持条件
- 保持第一个,请求第二个,如果拿不到,也不释放第一个
- 环路等待条件
- A拿到第一个请求第二个,B拿到第二个请求第一个
预防死锁
- 打破死锁条件
- 加锁顺序一致
- 避免锁为释放
- 资源一次性分配
避免死锁算法
- 死锁检测
- 银行家算法