当前位置: 首页 > news >正文

线程安全-同步与互斥/死锁

目录

前言

一、线程安全

1.互斥

2、死锁

3、同步


前言

提示:这里可以添加本文要记录的大概内容:

        临界资源—同时只能被一个线程/进程进行安全访问的资源,如共享内存、文件、硬件设备、全局变量等


提示:以下是本篇文章正文内容,下面案例可供参考

一、线程安全

        概念:多线程之间直接对于临界资源的访问操作是安全的 

        实现:通过同步与互斥实现。同步-通过一些条件控制,让线程对资源的获取更加合理。互斥-通过同一时间的唯一访问,让线程对资源的访问更加安全。

        最好看完所有在看这个——了解信号量的应该都听过同步与互斥 ,这里做一些说明:信号量主要用于线程间的同步,对有限资源的一种保护。大于0表示有可用资源,进程/线程可以操作,等于0没有可以资源,阻塞等待直到资源变得可用。在资源被锁定时,可以执行其他事件,直到该资源被释放

        互斥锁通常表示0或1,当上锁时,其他要获取该锁的线程将被阻塞,直到锁被释放,并且具有原子性,在加锁解锁的过程中不能被其他线程中断等等下面会说明

1.互斥

        互斥的实现是一个互斥锁—加锁和解锁的时候不能被打断。

        互斥锁:一个0/1的计数器—用于标记一个锁的状态。

作用:

        1、线程在访问临界资源前,进行加锁操作—在加锁前会判断,要锁的地方,是否已经上锁,如果已经上锁则无法加锁(这时候有两种情况,一种是阻塞线程直到解锁为止,另一种是不阻塞之间返回并且报错)。如果没有上锁则上锁。

        2、在访问临界资源完毕后会进行解锁操作,计数器归零,其他线程就可以进行加锁。

互斥锁的位置:内存中有一个互斥锁里面为0,cpu中有一个寄存器,寄存器为1,当进行加解锁的时候,将寄存器中的0和内存中的1进行交换之间一步到位。

接口: pthread_mutex_t mutex;       表示定义一个锁,锁名mutex使用的时候必须初始化

初始化:

        静态初始化:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;      

        动态初始化:pthread_mutex_t mutex;  

                              //int pthread_mutex_init(pthread_mutex *mutex, pthread_mutexarr_t

                                *arr);第二个参数通常置空,表示,指向互斥锁属性的指针。如
                              int ret = pthread_mutex_init(&mutex, NULL);

        静态初始化适合全局变量或静态变量,动态初始化使用局部变量和需要动态分配内存的

        地方 

加锁:

        int pthread_mutex_lock(pthread_mutex_t * mutex);        //阻塞加锁-等待直到解锁

        int pthread_mutex_trylock(pthread_mutex_t * mutex);    //非阻塞-若加锁过返回报错

解锁:

        int pthread_mutex_unlock(pthread_mutex_t * mutex);

销毁互斥锁:互斥锁不在需要则销毁

        int pthread_mutex_destroy(pthread_mutex_t * mutex);

        关于加锁有几点要注意

        1、加锁只是在要保护的临界区域前加锁,不能多保护其他的,否则会导致程序出现问题。

        2、解锁要考虑所有与临界资源相关的地方,判断会不会受到锁的影响

        3、互斥锁用完后要销毁 

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>//创建多个线程充当黄牛
//创建一个全局变量充当火车票
//黄牛每抢一张票,票的数量-1,抢票的前提是有票
//int ticket = 100;pthread_mutex_t mutex;  //定义一个锁void* hn(void* arg){while(1){usleep(3);pthread_mutex_lock(&mutex);    //上锁if( ticket > 0){    //如果票还有就继续抢printf("%p:抢到了一张票%d\n", (void*)pthread_self(), ticket);//线程IDticket--;pthread_mutex_unlock(&mutex);       //解锁}else{pthread_mutex_unlock(&mutex);       //解锁pthread_exit(NULL);     //没有票了就退出线程}}
}int main(){pthread_t thread[4];    //创建四个线程pthread_mutex_init(&mutex, NULL);       //定义一个锁for(int i = 0; i < 4; i++){int j = pthread_create(&thread[i], NULL,hn, NULL);if(j != 0){printf("线程创建失败");}}for(int i = 0; i < 4; i++){     //有四个线程就要等待四个线程退出pthread_join(thread[i], NULL);}pthread_mutex_destroy(&mutex);    //不用互斥锁了要销毁return 0;
}

 

2、死锁

概念:一个程序,由于锁资源的争抢不当导致程序流程卡死,无法继续推进的状态

产生死锁的四个必要条件:

        1、互斥条件:一个锁只能被一个线程加锁成功。

        2、不可剥夺条件:线程加的锁,只有自己能解锁,其他线程无法解锁

        3、请求与保持条件:进程(或线程)已经保持至少一个资源,但又提出了新的资源请

                                          求,而该资源已被其他进程(或线程)占有。此时,请求进程

                                        (或线程)被阻塞,但对自己已获得的资源保持不放。

        4、环路等待条件:线程A等待线程B占用的资源,线程B等待线程C占领的资源,线程C

              等待线程A占领的资源。

预防:破坏死锁产生的必要条件

        1、破坏环路条件:保证多个线程之间加解锁顺序一致。

        2、破坏请求与保持条件:采用非阻塞加锁,加完A锁后,若请求B失败,则释放A锁后

              重新请求。

3、同步

同步的实现:条件变量、互斥锁。

        条件变量:一个pcb的阻塞队列+两个接口(等待接口和通知接口)

                阻塞队列:用于存储被阻塞的线程,当线程不满足条件,会加入队列进行等待。

                等待接口:调用此接口,线程加入阻塞队列,进行等待唤醒。

                通知接口:满足唤醒条件,唤醒线程进入。

条件变量接口

        pthread_cond_t cond;        //条件变量类型

初始化条件变量

        int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr/通常置NULL);        //动态初始化

        int pthread_cond_t cond = PTHREAD_COND_INITIALIZER;        //静态初始化

等待条件变量

        int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);  

        参数:

                pthread_mutex_t *mutex:指向条件变量的指针。条件变量用于线程间的通知机

                                                              制,当一个线程改变了某个条件,并希望其他等待这

                                                              个条件的线程继续执行时,它会通知条件变量。

                pthread_mutex_t *mutex:指向互斥锁的指针。互斥锁用于保护共享资源,确保

                                                              同一时间只有一个线程可以访问这个资源。超时等待

                                                              条件变量

        int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const

                                                      struct timespec *abstime);        //在指定的时间段内,没有

                                                                                     被唤醒会自动醒来,并且返回一个错误

广播信号给条件变量

        int pthread_cond_broadcast(pthread_cond_t *cond);  //唤醒条件变量上等待的所有线程

发送信号给条件变量

        int pthread_cond_signal(pthread_cond_t *cond);//唤醒条件变量中的一个线程

销毁条件变量

        int pthread_cond_destroy(pthread_cond_t *cond);

注意点:

        1、同步的时候要注意判断防止死锁。

        2、解锁的时候要销毁条件变量

        一下面的情况为例:顾客和厨师 

 

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>//所需要拥有的,条件变量,互斥锁,线程。int meal = 0;   //0表示没有饭,1表示有饭pthread_mutex_t mutex;pthread_cond_t cond;
void* cook_(void* arg){while(1){pthread_mutex_lock(&mutex); //上锁if(meal != 0){pthread_cond_wait(&cond, &mutex);//有饭不需要做饭,阻塞线程,释放互斥锁}meal = 1;printf("饭做好了,请吃饭\n");pthread_mutex_unlock(&mutex);   //解锁pthread_cond_signal(&cond);     //唤醒阻塞线程sleep(1);}pthread_exit(NULL);
}
void* customers_(void* arg){while(1){pthread_mutex_lock(&mutex); //上锁if(meal == 0){pthread_cond_wait(&cond, &mutex);//有饭不需要做饭,阻塞线程,释放互斥锁}meal = 0;printf("吃完了,赶快做\n");pthread_mutex_unlock(&mutex);   //解锁pthread_cond_signal(&cond);     //唤醒阻塞线程sleep(2);}pthread_exit(NULL);
}
int main(){
//定义线程、互斥锁、事件变量pthread_t cook,customers;
//创建互斥锁,事件变量pthread_mutex_init(&mutex, NULL);pthread_cond_init(&cond, NULL);
//创建两个线程if(pthread_create(&cook, NULL, cook_, NULL) != 0){perror("创建厨师线程失败");return -1;}if(pthread_create(&customers, NULL, customers_, NULL) != 0){perror("创建顾客线程失败");return -1;}//等待线程退出pthread_join(cook, NULL);pthread_join(customers, NULL);
//销毁互斥锁、事件变量pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond);return 0;
}

 

 

注意事项:

        1、条件变量在使用的适合,先看资源获取条件释放满足、不满足则调用wait阻塞等待

         2、若条件变量在进行同步互斥的时候,有多种角色等待唤醒,则需要为每一个角色都

               创建一个条件变量进行分开等待和唤醒


http://www.mrgr.cn/news/59207.html

相关文章:

  • 山西农业大学20241025
  • Python字幕滚动:为视频添加专业级动态效果!
  • 项目文章 | 药学TOP期刊PRChIP-seq助力揭示激酶LIMK2促进梗死不良重构的机制
  • 如何对群辉docker进行简单更新升级
  • linux中级nginx实验
  • SQLI LABS | Less-10 GET-Blind-Time based-double quotes
  • 读取文件内容,并按数学成绩排名,之后输出显示
  • linux学习笔记 Ubuntu下的守护进程supervisor安装与多项目部署
  • 2024系统架构师---真题考试知识点
  • python如何通过json以及pickle读写保存数据
  • es实现自动补全
  • python 轮子是什么
  • 【Python】Whoosh:全流程自建搜索引擎
  • Linux之远程连接服务器
  • 【机器学习】股票数据爬取与展示分析(有代码链接)
  • 解析三相220V与三相380V变频器的关键差异
  • 初识Linux · 动静态库(incomplete)
  • 《 C++ 修炼全景指南:十七 》彻底攻克图论!轻松解锁最短路径、生成树与高效图算法
  • OCR应用之集装箱箱号自动识别技术,原理与应用
  • 3.1.1 平衡二叉树中改变区块属性,并分裂区块保持属性一致:MmSplitRegion()
  • RHCE笔记
  • 【LeetCode】修炼之路-0008- String to Integer (atoi)【python】
  • 数据结构(8.4_1)——简单选择排序
  • pixhawk 无人机 链接 遥控器
  • CSP-S 2024 游记
  • E - Permute K times 2