🌈个人主页:Fan_558
🔥 系列专栏:Linux
🌹关注我💪🏻带你学更多操作系统知识
文章目录
- 前言
- 一、死锁
- (1)死锁概念
- 二、同步
- (1)同步概念
- (2)条件变量
- (3)函数接口
- (4)代码实例
- 小结
前言
本文将会向你介绍死锁的概念,以及同步的概念和实现
一、死锁
(1)死锁概念
死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所占用不会释放的资源而处于的一种永久等待状态。
举个例子,F1与F2去买奶茶,此时F1和F2手中都有5块钱,奶茶需要十块钱,F1伸手向F2要钱,F2不肯,并说你把钱给我,F1自然也是不愿意的,那么他们手中都有五元钱,又互相申请对方的五元钱,双方都不肯放手
死锁四个必要条件
互斥条件:一个资源每次只能被一个执行流使用 请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放
不剥夺条件:一个执行流已获得的资源,在末使用完之前,不能强行剥夺
循环等待条件:若干执行流之间形成一种头尾相接的循环 等待资源的关系
避免死锁
破坏死锁的四个必要条件
加锁顺序一致
避免锁未释放的场景
资源一次性分配
Lock申请锁失败,线程会阻塞住,不会返回,trylock是申请锁的非阻塞版本
二、同步
(1)同步概念
同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步(前文我们提到公共独立自习室的例子,若一个人长时间持有钥匙,当他出门并把钥匙重新放回原处的时候,看到一大堆人在等他出来,他不想失去这个自习室,于是他又进去了,他那么轻松再次进去的原因就是他距离钥匙更近些,对钥匙的竞争力更大,线程也是如此)。若是其它线程长时间不能得到共享资源,势必会导致饥饿问题
前一篇文章我们也提到:定一个规矩:出来的人,不能立马重新申请锁,想要继续申请,必须排到队列的最后面,外面来的,必须排队
(2)条件变量
要么继续申请锁要么退出,不管要干什么做后续工作之前,必须把铃铛敲一下
把铃铛和队列称为条件变量(提供一种简单的通知机制:唤醒线程,并提供一个队列
让线程在等待队列中排队)
(3)函数接口
1、初始化条件变量
静态分配:
pthread_cond_t cond=PTHREAD_COND_INITIALIZER
注意:静态分配的条件变量不需要手动销毁
动态分配:
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict
attr);
参数:
cond:要初始化的条件变量
attr:NULL
2、利用条件变量等待资源就绪
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
参数:
cond:要在这个条件变量上等待
mutex:互斥量,后面详细解释
3、资源就绪后,主线程唤醒新线程来访问共享资源
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond)
4、销毁
int pthread_cond_destroy(pthread_cond_t *cond)
(4)代码实例
#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <stdint.h>
int cnt = 0;
//初始化锁、条件变量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
void* Count(void* args)
{
//线程分离
pthread_detach(pthread_self());
uint64_t number = (uint64_t)args;
while(true)
{
//加锁
pthread_mutex_lock(&mutex);
//等待
pthread_cond_wait(&cond, &mutex);
std::cout << "pthread: " << number << ", cnt: " << cnt++ << std::endl;
//解锁
pthread_mutex_unlock(&mutex);
sleep(3);
}
}
int main()
{
for(uint64_t i = 0; i < 5; i++)
{
pthread_t tid;
//创建线程
pthread_create(&tid, NULL, Count, (void*)i);
}
while(true)
{
sleep(1);
//唤醒等待队列头部的线程
//std::cout << "It's time to get up: one thread" << std::endl;
//pthread_cond_signal(&cond);
std::cout << "It's time to get up: all thread" << std::endl;
pthread_cond_broadcast(&cond);
}
return 0;
}
这段代码的逻辑本质是这样
加锁-----------
判断共享资源是否就绪
while(不满足){
>等待资源就绪}
>执行接下来的代码
code...
...
解锁-----------
三个疑问:
一、为什么判断条件要放在加锁解锁(临界资源里)之间 原因: 可是我们怎么知道我们要让一个线程去挂起呢(一旦调用该接口pthread_cond_wait(&cond, &mutex)线程就挂起了? 一定是临界资源不就绪,你怎么知道临界资源是就绪还是不就绪呢?
你判断出来的 怎么判断出来的呢?
那一定是有访问该临界资源,否则怎么知道就绪还是不就绪呢。 那么为了防止多并发问题,那么我们是不是应该将会访问临界资源的判断加锁呢?
必须是的,也就是判断必须在加锁之后
二、为什么pthread_cond_wait也要放在加锁和解锁之间
因为当条件不满足的时候就需要去等待
三、pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t
*restrict mutex)为什么第二个参数携带锁因为当一个线程进到临界区内时,当资源不就绪,该线程就应该去等待,可是如果该线程不释放这把锁就去等待的话,那么下一个线程该如何进入临界区内呢,锁只有一把,所以pthread_cond_wait第二个参数传入一把锁的原因是为了让线程去等待前释放锁
答疑完了,让我们来看看运行结果如何
每次唤醒一个线程,各个线程按照特定的次序访问这批资源
唤醒所有线程
小结
今日的分享就到这里啦,如果本文存在疏漏或错误的地方还请您能够指出!