_Linux多线程-死锁Linux线程同步篇
创始人
2024-05-11 08:35:53
0

文章目录

  • 1. 死锁
    • 死锁四个必要条件
    • 避免死锁
    • 避免死锁算法(了解)
  • 2. Linux线程同步
    • 线程同步出现的背景
    • 条件变量
    • 同步概念与竞态条件
    • 条件变量函数
      • 1. 初始化
      • 2. 销毁
      • 3. 等待条件满足
      • 4. 唤醒等待
    • 小结
    • 测试实验

1. 死锁

  • 死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站用不会释放的资源而处于的一种永久等待状态。
    在这里插入图片描述

死锁四个必要条件

  • 互斥条件:一个资源每次只能被一个执行流使用。
  • 请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放。
  • 不剥夺条件:一个执行流已获得的资源,在末使用完之前,不能强行剥夺。
  • 循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系。

就好比两个小朋友都只有5毛;两个小朋友都想吃1块钱的棒棒糖;两位小朋友在商量的过程中,都拿不到对方的五毛钱更不用说吃棒棒糖了。

避免死锁

  • 破坏死锁的四个必要条件
  • 加锁顺序一致
  • 避免锁未释放的场景
  • 资源一次性分配

避免死锁算法(了解)

  • 死锁检测算法(了解)
  • 银行家算法(了解)

2. Linux线程同步

线程同步出现的背景

肯定是可以避免死锁的情况出现

    1. 频繁的申请到资源和对方的资源了(别人怎么办)—即可能造成别人的饥饿的问题
      • 例如:学校食堂打饭,前面总有人插队,你就可能吃不上饭(就好比一个轻量级进程的优先级很高一样)。
    1. 有一个线程再访问临界资源;你这个线程光过来询问就太过于浪费时间了(可以引入条件变量)
      • 例如:有一次你去买买件商品,商品没货;你就 天天来商店去询问售货员。这种解决方法太浪费时间了,还不如加个微信,有货的时候让售货员通知你就可以了。

条件变量

  • 当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。
    • 例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。

同步概念与竞态条件

  • 同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步。
  • 竞态条件:因为时序问题,而导致程序异常,我们称之为竞态条件。

条件变量函数

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_destroy(pthread_cond_t *cond)

3. 等待条件满足

int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
参数:
cond:要在这个条件变量上等待
mutex:互斥量(线程互斥篇里的锁)

4. 唤醒等待

int pthread_cond_broadcast(pthread_cond_t *cond);
全部线程一起唤醒
int pthread_cond_signal(pthread_cond_t *cond);
线程一个一个按顺序来唤醒

小结

  • 引入同步: 主要是为了解决,访问临界资源合理性问题的

    • 按照一定的顺序,进行临界资源的访问—线程同步
  • 条件变量

  • 当我们申请临界资源前-> 先要做临界资源是否存在的检测->要做检测的本质也是访问临界资源!

    • 结论: 对临界资源的检测,也一定是需要在加锁和解锁之间的!
    • 常规方式要检测条件就绪 ,注定了我们必须频繁申请和释放锁(需要条件变量)
  • 条件变量

    • 1不要让线程在频繁自己检测啦—等待
    • 2.当条件就绪的时候,通知对应的线程,让他来进行资源申请和访问!

测试实验

  • 实验代码块:
#include 
#include 
#include using namespace std;#define THREAD_NUM 4                   // 线程个数
typedef void (*funcs)(const std::string &name,pthread_mutex_t *pmtx, pthread_cond_t *pcond); // 函数指针
// pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
// pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;volatile bool quit = false;            // 退出条件变量class threadData
{
public:threadData(const string &name, funcs func, pthread_mutex_t *mtx, pthread_cond_t *cond): _name(name), _func(func), _mtx(mtx), _cond(cond){} // 类初始化
public:string _name; // 线程名字funcs _func;pthread_mutex_t *_mtx;pthread_cond_t *_cond;
};void Func1(const std::string &name,pthread_mutex_t *pmtx, pthread_cond_t *pcond)
{while (!quit){pthread_mutex_lock(pmtx);           // 申请锁pthread_cond_wait(pcond, pmtx); // 默认该线程在执行的时候,wait代码被执行,当前线程会被立即被阻塞cout << name << "...我正在执行数据扫描任务..." << endl;pthread_mutex_unlock(pmtx); // 解锁}
}
void Func2(const std::string &name,pthread_mutex_t *pmtx, pthread_cond_t *pcond)
{while (!quit){pthread_mutex_lock(pmtx);           // 申请锁pthread_cond_wait(pcond, pmtx); // 默认该线程在执行的时候,wait代码被执行,当前线程会被立即被阻塞cout << name << "...我正在执行下载任务..." << endl;pthread_mutex_unlock(pmtx); // 解锁}
}
void Func3(const std::string &name,pthread_mutex_t *pmtx, pthread_cond_t *pcond)
{while (!quit){pthread_mutex_lock(pmtx);           // 申请锁pthread_cond_wait(pcond, pmtx); // 默认该线程在执行的时候,wait代码被执行,当前线程会被立即被阻塞cout << name << "...我正在执行数据刷新任务..." << endl;pthread_mutex_unlock(pmtx); // 解锁}
}
void Func4(const std::string &name,pthread_mutex_t *pmtx, pthread_cond_t *pcond)
{while (!quit){pthread_mutex_lock(pmtx);           // 申请锁pthread_cond_wait(pcond, pmtx); // 默认该线程在执行的时候,wait代码被执行,当前线程会被立即被阻塞cout << name << "...我正在执行广播任务..." << endl;pthread_mutex_unlock(pmtx); // 解锁}
}
void *Entry(void *args)
{threadData *td = (threadData *)args;td->_func(td->_name, td->_mtx, td->_cond); // 函数回调delete td;return nullptr;
}int main()
{pthread_mutex_t mtx;pthread_cond_t cond;// mtx && cond 初始化pthread_mutex_init(&mtx, nullptr);pthread_cond_init(&cond, nullptr);pthread_t tids[THREAD_NUM]; // 线程idfuncs func_t[THREAD_NUM] = {Func1, Func2, Func3, Func4};for (int i = 0; i < THREAD_NUM; ++i){string name = "thread ";name += to_string(i + 1);threadData *td = new threadData(name, func_t[i], &mtx, &cond);// 创建线程pthread_create(tids + i, nullptr, Entry, td);}sleep(5);// cond---唤醒int cnt = 10;while (cnt){cout << "线程正在唤醒 " << cnt-- << endl;pthread_cond_signal(&cond);sleep(1);cout << "唤醒成功" << endl;}quit = true;pthread_cond_broadcast(&cond);  //这一次,让其它线程出来(剩下的线程全部唤醒)for (int i = 0; i < THREAD_NUM; ++i){// 线程等待pthread_join(tids[i], nullptr);cout << "thread " << tids[i] << "...quit" << endl;}cout << THREAD_NUM << "个线程任务结束" << endl;//  mtx && cond销毁pthread_mutex_destroy(&mtx);pthread_cond_destroy(&cond);return 0;
}
  • 实验示意图:
    在这里插入图片描述

相关内容

热门资讯

【NI Multisim 14...   目录 序言 一、工具栏 🍊1.“标准”工具栏 🍊 2.视图工具...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
Android|无法访问或保存... 这个问题可能是由于权限设置不正确导致的。您需要在应用程序清单文件中添加以下代码来请求适当的权限:此外...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
AsusVivobook无法开... 首先,我们可以尝试重置BIOS(Basic Input/Output System)来解决这个问题。...
ASM贪吃蛇游戏-解决错误的问... 要解决ASM贪吃蛇游戏中的错误问题,你可以按照以下步骤进行:首先,确定错误的具体表现和问题所在。在贪...
月入8000+的steam搬砖... 大家好,我是阿阳 今天要给大家介绍的是 steam 游戏搬砖项目,目前...