一、互斥锁
1.概念
现代操作系统中,一般都是多任务的,即同时有大量可调度的实体在运行。在这样情况下可能:
- 都需要访问、调用同一种资源
- 多个任务的运行有依赖关系,某个任务的运行依赖另一个任务
同步和互斥就是解决这两个问题的。
互斥:一个进程或线程在运行某一程序片段时,另一个进程或线程不允许运行,只有等当前进程运行完毕才可以。互斥具有唯一性和排它性。
同步:程序片段依赖于先后顺序,后面的任务依赖前面的数据。
2.互斥锁Mutex介绍
线程中有一把锁,叫做互斥锁,有两种状态,分别为加锁和解锁。
互斥锁操作流程:
- 在访问共享资源后临界区域前,对互斥锁进行加锁。
- 在访问完成后释放互斥锁导上的锁。
- 枷锁后,任何线程试图再枷锁,将被阻塞,直到解锁。
互斥锁数据类型:pthread_mutex_t。
3.初始化互斥锁函数
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
功能:
初始化一个互斥锁。
参数:
mutex:互斥锁地址。类型是 pthread_mutex_t 。
attr:设置互斥量的属性,通常可采用默认属性,即可将 attr 设为 NULL。
可以使用宏PTHREAD_MUTEX_INITIALIZER静态初始化互斥锁,比如:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
返回值:
成功:0,成功申请的锁默认是打开的。
失败:非 0 错误码
4.pthread_mutex_destroy函数
#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
功能:
销毁指定的一个互斥锁。互斥锁在使用完毕后,必须要对互斥锁进行销毁,以释放资源。
参数:
mutex:互斥锁地址。
返回值:
成功:0
失败:非 0 错误码
5.pthread_mutex_lock函数
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
功能:
对互斥锁上锁,若互斥锁已经上锁,则调用者阻塞,直到互斥锁解锁后再上锁。
参数:
mutex:互斥锁地址。
返回值:
成功:0
失败:非 0 错误码
int pthread_mutex_trylock(pthread_mutex_t *mutex);
功能:
调用该函数时,若互斥锁未加锁,则上锁,返回 0;
若互斥锁已加锁,则函数直接返回失败,即 EBUSY。
6.pthread_mutex_unlock函数
#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t *mutex);
功能:
对指定的互斥锁解锁。
参数:
mutex:互斥锁地址。
返回值:
成功:0
失败:非0错误码
7.死锁
7.1概念
在两个或两个以上进程执行过程中,因为竞争资源或彼此通信,导致所有进程共同阻塞,无外力情况下程序无法继续进行下去,这就是死锁,这些永远在等待的进程被称为死锁进程。
Q:彼此通信为什么导致死锁?
A:
7.2引起死锁的原因
- 竞争不可抢占资源引发死锁
- 竞争可消耗资源引发死锁:
有p1,p2,p3三个进程,p1向p2发送消息并接受p3发送的消息,p2向p3发送消息并接受p1的消息,p3向p1发送消息并接受p2的消息,如果设置是先接到消息后发送消息,则所有的消息都不能发送,这就造成死锁。
3.进程推进顺序不当引发死锁:
有进程p1,p2,都需要资源A,B,本来可以p1运行A --> p1运行B --> p2运行A --> p2运行B,但是顺序换了,p1运行A时p2运行B,容易发生第一种死锁。互相抢占资源。
7.3 引发死锁的必要条件
- 互斥条件
- 请求和保持条件:当前进程占用了至少一个资源,它不释放资源的情况下,提出新要求--想要对其它进程的资源占有。
- 不可抢占条件:进程没有使用完资源,不能被抢占。
- 循环等待条件:必然存在一个循环链。
7.4处理死锁的思路
- 预防死锁:破坏四个必要条件的一个或多个
- 避免死锁:在资源动态分配过程中,用某种方式防止系统进入不安全的状态。
- 处理死锁:运行时出现死锁,能及时发现死锁,把程序解脱出来
- 接触死锁:发生死锁后,解脱进程,通常撤销进程,回收资源,再分配给正处于阻塞状态的进程。
7.5预防死锁方法
- 破坏请求和保持条件:
协议1:
所有进程开始前,必须一次性地申请所需的所有资源,这样运行期间就不会再提出资源要求,破坏了请求条件,即使有一种资源不能满足需求,也不会给它分配正在空闲的资源,这样它就没有资源,就破坏了保持条件,从而预防死锁的发生。
协议2:
允许一个进程只获得初期的资源就开始运行,然后再把运行完的资源释放出来。然后再请求新的资源。
- 破坏不可抢占条件:当一个已经保持了某种不可抢占资源的进程,提出新资源请求不能被满足时,它必须释放已经保持的所有资源,以后需要时再重新申请。
- 破坏循环等待条件:对系统中的所有资源类型进行线性排序,然后规定每个进程必须按序列号递增的顺序请求资源。假如进程请求到了一些序列号较高的资源,然后有请求一个序列较低的资源时,必须先释放相同和更高序号的资源后才能申请低序号的资源。多个同类资源必须一起请求。
二、读写锁
1.概述
若某个线程有互斥锁,其它线程阻塞了对临界区的共享资源进行访问。但是共享资源被多个线程共同读是被允许的,所以可以使用读写锁,使其能被多个线程读,只能被一个线程写。
读写锁特点:
1)如果有其它线程读数据,则允许其它线程执行读操作,但不允许写操作。
2)如果有其它线程写数据,则其它线程都不允许读、写操作。
读写锁分为读锁和写锁,规则如下:
1)如果某线程申请了读锁,其它线程可以再申请读锁,但不能申请写锁。
2)如果某线程申请了写锁,其它线程不能申请读锁,也不能申请写锁。
POSIX中读写锁数据类型:pthread_rwlock_t
2.pthread_rwlock_init函数
#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
const pthread_rwlockattr_t *restrict attr);
功能:
用来初始化 rwlock 所指向的读写锁。
参数:
rwlock:指向要初始化的读写锁指针。
attr:读写锁的属性指针。如果 attr 为 NULL 则会使用默认的属性初始化读写锁,否则使用指定的attr 初始化读写锁。
可以使用宏 PTHREAD_RWLOCK_INITALIZER 静态初始化读写锁,比如
pthread_rwlock_t = PTHREAD_RWLOCK_INITALIZER;
这种方法等价于使用 NULL 指定的 attr 参数调用 pthread_rwlock_init() 来完成动态初始化,不同之处在于PTHREAD_RWLOCK_INITIALIZER 宏不进行错误检查。
返回值:
成功:0,读写锁的状态将成为已初始化和已解锁。
失败:非 0 错误码。
3.pthread_rwlock_destroy函数
#include <pthread.h>
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
功能:
用于销毁一个读写锁,并释放所有相关联的资源(所谓的所有指的是由 pthread_rwlock_init() 自动申请的资源) 。
参数:
rwlock:读写锁指针。
返回值:
成功:0
失败:非 0 错误码
4.pthread_rwlock_rdlock函数
#include <pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
功能:
以阻塞方式在读写锁上获取读锁。
如果没有写者持有该锁,或者没有写者阻塞在该锁上,则调用线程会获取读锁。
如果调用线程未获取读锁,则它将阻塞直到它获取了该锁。一个线程可以在一个读写锁上多次执行读锁定。
线程可以成功调用 pthread_rwlock_rdlock() 函数 n 次,但是之后该线程必须调用 pthread_rwlock_unlock() 函数 n 次才能解除锁定。
参数:
rwlock:读写锁指针。
返回值:
成功:0
失败:非 0 错误码
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
用于尝试以非阻塞的方式来在读写锁上获取读锁。
如果有任何的写者持有该锁或有写者阻塞在该读写锁上,则立即失败返回。
5. pthread_rwlock_wrlock函数
#include <pthread.h>
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
功能:
在读写锁上获取写锁(写锁定)。
如果没有写者持有该锁,并且没有写者读者持有该锁,则调用线程会获取写锁。
如果调用线程未获取写锁,则它将阻塞直到它获取了该锁。
参数:
rwlock:读写锁指针。
返回值:
成功:0
失败:非 0 错误码
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
用于尝试以非阻塞的方式来在读写锁上获取写锁。
如果有任何的读者或写者持有该锁,则立即失败返回。
6.pthread_rwlock_unlock函数
#include <pthread.h>
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
功能:
无论是读锁或写锁,都可以通过此函数解锁。
参数:
rwlock:读写锁指针。
返回值:
成功:0
失败:非 0 错误码