前言
在linux操作系统下。线程实际上上是应用层的进程。linux中并没有一个详细的数据结构来描述线程。所以在linux中,线程的实现是创建一个轻量级的进程。
当我们创建两个线程时,就可以并行执行两个任务。尽管我们创建两个进程也可以达到并发的目的,但是我们创建两个线程的代价会比创建两个进程的代价要小很多。
linux操作系统中,通过pcb来管理一个进程的资源,当我们在这个进程中创建线程时,操作系统会在创建一个pcb 这两个pcb(线程)会共享这个进程的资源。(ps: 这里并不是完全共享,线程依旧会分配自己的线程栈等等)
同步和互斥
线程安全问题
当我们多个线程都在执行一段代码(函数)或者任务时,会出现不同的结果,如果这个结果影响到共享的资源时,例如全局变量或静态量时,就会出现错误的预期。
重入: 我们会看到一些“可重入函数”和“不可重入函数”可重入就指的是当我们多个线程同时调用这个函数时,函数的结果不会出现问题,这样的函数我们就称为可重入函数。反之就是不可重入函数。
所以如果是可重入函数,则该函数一定是线程安全的。
同步机制
为了避免线程出现不安全的情况。所谓的同步,就是我们让各个任务按照约定的顺序合理运行。当我们多个任务同时去访问共享资源时,也就是资源竞争,这种时候就需要让几个任务互斥,即同时只能有一个任务访问这个共享资源。
所以同步和互斥一个为了处理多个任务对资源的访问机制。同步让任务按规则进行,互斥则避免让任务出现资源竞态冲突。
条件变量函数
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict
attr);
// 条件变量初始化
//cond:要初始化的条件变量
//attr:NULL
int pthread_cond_destroy(pthread_cond_t *cond)
//条件变量销毁
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
//等待条件满足
//cond:要在这个条件变量上等待
//mutex:互斥量(互斥锁)
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
// 唤醒等待
锁
为了实现互斥,我们就需要使用“锁”
锁的作用就是对临界区的资源进行保护。
但是当我们使用锁时,锁实际上也是一种临界资源。所以我们进行锁操作时,必须要保证原子性
互斥锁
互斥锁就是指在访问临界资源时进行加锁操作。这样当有另一个任务需要访问相同的临界资源时,就会被锁阻塞。直到访问临界资源的任务释放锁。然后系统会唤醒正在被阻塞的任务。
自旋锁
自旋锁的主要特征是,在需要访问临界资源时,发现临界资源已被上锁,自旋锁不会阻塞等待系统唤醒,而是原地轮询临界资源的锁是否释放。
自旋锁的优点就是避免了系统的唤醒,在临界区资源处理代码较少时,可以有效提高程序效率。
读写锁
读写锁也叫共享互斥锁。就是分为读模式和写模式。读模式通行,写模式互斥。例如,当我们有两个任务需要对临界资源访问时,但是这两个任务都只需要读取临界资源的内容,那么即使这两个资源同时访问,也不会出现问题,
但是这个时候我们不能让一个会修改临界资源的任务访问。所以在读模式时,会对写模式上锁。
当一个会修改临界资源的任务访问时,这时候无论是读还是写都需要被阻塞。所以在需要修改临界资源的任务访问时,我们需要同时对读模式和写模式上锁。
死锁
死锁是指在各个进程或者线程占据着一部分临界资源,同时申请了被其他进程线程占用的临界资源,从而多个进程都进入了永久等待的状态。
死锁的条件
- 互斥条件:一个临界资源每次只能被一个执行流使用。
- 请求与保持:一个执行流因申请资源而被阻塞时,不会对已占有的临界资源释放。
- 不剥夺条件:已经被占有的资源,在没有被主动释放前,不能强行剥夺。
- 循环等待:若干线程 进程间形成对资源申请循环。
破坏死锁
- 破坏死锁的四个条件
- 加锁顺序一致
- 避免锁为释放的场景
- 资源一次性分配
银行家算法
银行家算法是一个非常著名的避免死锁的算法。
这是将操作系统看做银行家,对操作系统存在的资源比作银行资金进行管理的思想。
为了保证资金(资源)的安全,所以制定了一些规则。
操作系统按照银行家制定的规则为进程分配资源,当进程首次申请资源时,要测试该进程对资源的最大需求量,如果系统现存的资源可以满足它的最大需求量则按当前的申请量分配资源,否则就推迟分配。当进程在执行中继续申请资源时,先测试该进程本次申请的资源数是否超过了该资源所剩余的总量。若超过则拒绝分配资源,若能满足则按当前的申请量分配资源,否则也要推迟分配。