互斥
概念
通过同一时间内访问的唯一性保证临界资源访问的安全性。
互斥的实现
通过 互斥锁/互斥量 来实现。
互斥锁
本身是一个只有 0/1 的计数器,描述了一个临界资源当前的访问状态。所有的执行流在访问临界资源前都必须判断当前的临界资源是否允许访问。如不允许访问则执行流等待,否则可以访问临界资源,但是在访问期间需要将临界资源的访问状态改为不可访问状态,防止其他的外来访问干扰。
函数接口
#include <pthread.h> 头文件
1. 定义互斥锁变量
pthread_mutex_t mutex;
2. 初始化互斥锁
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
在LinuxThreads实现中,pthread_mutex_t是一个结构,而PTHREAD_MUTEX_INITIALIZER则是一个结构常量。
参数: mutex: 互斥锁变量
attr: 互斥锁属性,通常置NULL
3. 加锁
<1> int pthread_mutex_lock(pthread_mutex_t *mutex);
如果当前状态不能加锁(别人已经上锁),则阻塞等待,直到加锁成功后返回
参数:
mutex: 互斥锁变量
<2> int pthread_mutex_trylock(pthread_mutex_t *mutex);
尝试加锁,如果当前状态无法加锁,则立即返回报错。
参数:
mutex: 互斥锁变量
4. 解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
参数:
mutex: 互斥锁变量
5. 销毁互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
参数:
mutex: 互斥锁变量
同步
概念
通过条件判断保证对临界资源访问的合理性
实现
因为只提供了两个接口: 执行流等待接口、执行流唤醒接口。所以需要线程自身对自身进行条件判断,看自身是否满足;满足则获取临界资源,不满足则让执行流等待,直到满足之后再将其唤醒。
函数接口
#include<pthread.h> 头文件
1. 定义条件变量
pthread_cond_t cond;
2. 初始化条件变量
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
在LinuxThreads实现中,pthread_cond_t是一个结构,而PTHREAD_COND_INITIALIZER则是一个结构常量。
参数: cond: 条件变量
attr: 属性,通常置NULL
3. 线程等待、休眠
<1> int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
当获取资源的条件不满足时,让执行流等待休眠,直到被唤醒后才会返回,否则一直等待休眠
该接口必须配互斥锁使用
该接口自动包含了 解锁、等待休眠、被唤醒后自动加锁 这三步操作
参数:
cond: 条件变量
mutex: 互斥锁
<2> int pthread_cond_timedwait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex,
const struct timespec *restrict abstime);
当资源获取的条件不满足时,让执行流等待休眠,但是超过一定时间后,执行流会自动醒来并退出
参数:
cond: 条件变量
mutex: 互斥锁
abstime: 设置超时自醒的函数接口
4. 唤醒线程
<1> int pthread_cond_broadcast(pthread_cond_t *cond);
唤醒至少一个线程
参数:
cond 条件变量
<2> int pthread_cond_signal(pthread_cond_t *cond);
唤醒全部线程
参数:
cond 条件变量
5. 销毁条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
参数:
cond 条件变量
总结
- 互斥保证临界资源访问的安全性,不保证合理性。
- 同步保证临界资源访问的合理性,不保证安全性。
使用注意点
- 对判断条件变量是否满足的过程(线程该等待休眠还是该继续访问)应该用 while 来循环判断。
- 多角色的线程应该设置不同的条件变量,防止唤醒过程出错。
具体的代码实现
死锁
概念
多个执行流对临界资源进行争抢式访问,由于访问顺序的不当,造成互相等待以至于执行流无法继续正常向下执行。
产生死锁的必备条件
- 互斥条件:
一个资源同一时间只能有一个线程去访问,一个线程加锁其他线程无法加锁。 - 不可剥夺条件
该线程加的锁只能由该线程解锁,其他线程无权干涉。 - 请求与保持条件
一个线程对A加锁后再次去请求对B加锁;若其无法对B加锁,也不对A解锁。 - 环路等待条件
q 线程对A加锁后再次去请求对B加锁;此时 p 线程对B加锁后再次去请求对A加锁。
死锁的避免
银行家算法/死锁检查算法
银行家算法的思想即操作
- 设置三张记录表,一张记录当前所有的资源、一张记录已分配的资源、一张记录当前需要的资源。然后进行判断,判断若给一个执行流分配了它所需的资源后,是否会达成环路等待条件,是的话则不分配,反之则分配(破坏环路等待条件)。
- 后续若不能继续分配,可以进行回滚,释放当前已分配的资源(破坏等待与请求条件)。
- 非阻塞加锁,若不能对其他资源加锁则释放手中的资源(破坏请求与等待条件)。
银行家算法的具体代码操作在我的博客中有上传,很久前写的有点粗糙大家理解一下