一、互斥锁

1.概念

现代操作系统中,一般都是多任务的,即同时有大量可调度的实体在运行。在这样情况下可能:

  • 都需要访问、调用同一种资源
  • 多个任务的运行有依赖关系,某个任务的运行依赖另一个任务

同步和互斥就是解决这两个问题的。

 

互斥:一个进程或线程在运行某一程序片段时,另一个进程或线程不允许运行,只有等当前进程运行完毕才可以。互斥具有唯一性和排它性。

 

同步:程序片段依赖于先后顺序,后面的任务依赖前面的数据。

2.互斥锁Mutex介绍

线程中有一把锁,叫做互斥锁,有两种状态,分别为加锁和解锁。

互斥锁操作流程:

  1. 在访问共享资源后临界区域前,对互斥锁进行加锁。
  2. 在访问完成后释放互斥锁导上的锁。
  3. 枷锁后,任何线程试图再枷锁,将被阻塞,直到解锁。

互斥锁数据类型: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引起死锁的原因

  1. 竞争不可抢占资源引发死锁
  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 引发死锁的必要条件

  1. 互斥条件
  2. 请求和保持条件:当前进程占用了至少一个资源,它不释放资源的情况下,提出新要求--想要对其它进程的资源占有。
  3. 不可抢占条件:进程没有使用完资源,不能被抢占。
  4. 循环等待条件:必然存在一个循环链。

7.4处理死锁的思路

  1. 预防死锁:破坏四个必要条件的一个或多个
  2. 避免死锁:在资源动态分配过程中,用某种方式防止系统进入不安全的状态。
  3. 处理死锁:运行时出现死锁,能及时发现死锁,把程序解脱出来
  4. 接触死锁:发生死锁后,解脱进程,通常撤销进程,回收资源,再分配给正处于阻塞状态的进程。

7.5预防死锁方法

  1. 破坏请求和保持条件: 协议1:
    所有进程开始前,必须一次性地申请所需的所有资源,这样运行期间就不会再提出资源要求,破坏了请求条件,即使有一种资源不能满足需求,也不会给它分配正在空闲的资源,这样它就没有资源,就破坏了保持条件,从而预防死锁的发生。
    协议2:
    允许一个进程只获得初期的资源就开始运行,然后再把运行完的资源释放出来。然后再请求新的资源。
  2. 破坏不可抢占条件:当一个已经保持了某种不可抢占资源的进程,提出新资源请求不能被满足时,它必须释放已经保持的所有资源,以后需要时再重新申请。
  3. 破坏循环等待条件:对系统中的所有资源类型进行线性排序,然后规定每个进程必须按序列号递增的顺序请求资源。假如进程请求到了一些序列号较高的资源,然后有请求一个序列较低的资源时,必须先释放相同和更高序号的资源后才能申请低序号的资源。多个同类资源必须一起请求。

 

二、读写锁

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 错误码