线程安全-同步与互斥/死锁
目录
前言
一、线程安全
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、若条件变量在进行同步互斥的时候,有多种角色等待唤醒,则需要为每一个角色都
创建一个条件变量进行分开等待和唤醒