Linux之线程安全

12 篇文章 0 订阅
11 篇文章 0 订阅

上一节我讲了Linux线程的一些基础知识,返现多线程是不安全的,会引发安全问题,所以今天就来讲一下Linux线程安全一节的内容!

1.线程安全概念

线程安全:多个线程同时操作临界资源不会出现数据二义性

2.线程安全的实现

这里要引入两个概念:同步与互斥
同步:临界资源访问的时序可控
互斥:临界资源访同一时间的唯一访问性
临界资源:多线程执行流共享的资源
原子性:不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成。
可重入/不可重入:多个执行流中是否可以同时进入函数运行而不会出现问题,是否在线程函数中对临界资源进行了非原子操作。

下面来举个例子:(为了举例,当然这是一种理想情况,不可能不存在竞争)
一个系统中多个线程必然要共享一些系统资源,如共享CPU,共享I/O设备。
以打印机为例,线程A在使用打印机时,其他线程不能使用而只能等待,这就是互斥,保证临界资源(打印机)的同一时间的唯一访问性。
线程A用完了,它后面的线程再逐个接着使用,这就是同步,保证临界资源访问的时序可控。

在访问临界资源时会产生资源竞争问题,这就需要实现互斥,那么如何实现互斥呢?
这就需要用到互斥锁(mutex):
互斥锁原理图:
在这里插入图片描述
上图中就用到了互斥锁变量mutex,当一个线程访问临界资源时就加锁,其他线程只能等待,无法访问,等到该线程访问完毕解锁后,其他线程才能继续访问。

互斥锁的操作步骤:(用到的接口)
1.定义互斥锁变量
pthread_mutex_t mutex

2.初始化互斥锁变量
pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
mutex: 要初始化的互斥量
attr:NULL

3.加锁/解锁
pthread_mutex_lock(pthread_mutex_t *mutex);
阻塞加锁:加不上锁就阻塞
pthread_mutex_trylock(pthread_mutex_t *mutex);
非阻塞加锁:加不上锁就报错返回
pthread_mutex_timelock(pthread_mutex_t *restrict mutex,
const struct timespec *restrict abs_timeout)
限时阻塞加锁
pthread_mutex_unlock(pthread_mutex_t *mutex);
解锁
返回值:0 失败:错误号

4.销毁互斥锁
pthread_mutex_destroy(pthread_mutex_t *mutex);

加了锁也不是一定就不会出问题,比如会出现死锁情况如下图!!!
在这里插入图片描述

死锁:因为对一些无法加锁的锁,进行加锁而导致程序卡死
产生:对资源的竞争以及进程/线程加锁的推进顺序不当
死锁产生的四个必要条件:
1.互斥操作(我操作的时候别人不能操作)
2.不可剥夺条件(我加的锁.别人不能解)
3.请求与保持条件(拿着手里的,请求其他的,其他的请求不到,手里的也不放)
4.环路等待条件
产生场景:加锁/解锁顺序不同
预防死锁:破坏必要条件
避免死锁:死锁检测算法,银行家算法

银行家算法:
这个算法为什么叫银行家算法呢?是因为这个算法同样适用于银行的贷款业务。

当一个进程申请使用资源的时候,银行家算法通过先试探分配给该进程资源,然后通过安全性算法判断分配后的系统是否处于安全状态,若不安全则试探分配作废,让该进程继续等待。< font>

最后通过安全判定算法通过的序列就称为安全序列,这个安全序列就不会产生类似死锁的情况。
假设有进程P1,P2,,,,Pn
则安全序列应该满足:Pi(0<i<n)需要的资源 = 分配给Pj(0<j<i)的资源 + 剩余资源< font>
为什么还要加上分配给Pj的资源呢?你想想银行贷款他总不可能只出不进吧?别人贷了款总要还款的啊。

int n,m    //系统中的进程数n和资源数m
int Avaliable[1..m]         //资源当前可用总量
int Allocation[1...n,1...m]     //当前分配给各个进程的各种资源数
int Need[1...n,1...m]       //当前每个进程还需分配的各种资源数量
int Work[1...m]         //当前可分配的资源
bool Finish[1...n]     //进程是否完成

银行家算法->安全判定算法
1.初始化
Work = Available (动态记录当前剩余资源)
Finish[i] = false (设当前所有进程均未完成)

2.查找可执行的Pi (未完成的话但是剩余资源能满足其需要,这样也算可以完成)
Finish[i] = false
Need[i]<=Work
如果没有这样的进程Pi,就跳转到第4步

3.如果没有 Pi一定可以完成,并归还分配给其的资源
Finish[i] = true
Work = Work + Allocation[i]
继续返回第2步查找

4.如果所有进程Pi都能完成,即Finish[i] = true
则系统处于安全状态,否则是不安全状态

实现伪代码:

Boolean Found;
Work = Available ;
Finish[1..n] = false;
while(true){
	Found = false;
	for(int i=0;i<n;i++){
		if(Finish[i]==false&&Need[i]<=Work){
			Work = Work + Allocation[i];
			Finish[i] = true;
			Found = true;
 		}
	}
	if(Found == false){
		break;
	}
}
for(int i=0;i<n;i++){
	if(Finish[i]==false){
		return "Unfinished"
	}
}

同步的实现:
临界资源的访问合理性—生产出来才能使用,没有资源则等待(死等),生产资源后唤醒等待。
条件变量:例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中。这种情 况就需要用到条件变量。

1.定义条件变量
pthread_cond_t n

2.初始化条件变量
pthread_cond_init(&n)

3.等待/唤醒
pthread_cond_wait(&n,&mutex)
pthread_cond_signal(&n)

4.销毁条件变量
pthread_cond_destroy(&n)

条件变量为什么要搭配互斥锁使用?
因为条件变量本身只提供等待与唤醒功能,具体什么时候等待需要用户来进行判断,这个条件的判断,通常涉及到临界资源的操作(其他线程要通过修改条件,来促使条件满足)而这个临界资源的操作应该收保护,因此搭配互斥锁一起使用。< font>

最后给大家举个例子:(利用同步与互斥)
实现了一个简单的黄牛抢票程序

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<pthread.h>
  5 
  6 int ticket = 100;
  7 pthread_mutex_t mutex;
  8 
  9 void* pth_start(void* arg){//pthread_create 入口函数
 10     while(1){
 11         pthread_mutex_lock(&mutex);//在访问临界资源前加互斥锁
 12         if(ticket>0){
 13             printf("第 %d 黄牛,抢了张票: %d\n",(int)arg,ticket);
 14             ticket--;
 15         }
 16         else{
 17             pthread_mutex_unlock(&mutex);//如果没票了也要解锁,否为后面将会死等
 18             pthread_exit(NULL);
 19         }
 20         pthread_mutex_unlock(&mutex);//一个黄牛抢到票后要解锁,其他的黄牛可以抢票
 21     }
 22     return NULL;
 23 }
 24 
 25 int main(int argc,char* argv[])
 26 {
 27     pthread_t tid[4];
 28     int i=0,ret;
 29     //互斥锁初始化
 30     pthread_mutex_init(&mutex,NULL);
 31     //创建四个线程来充当黄牛抢票
 32     for(;i<4;i++){
 33         ret = pthread_create(&tid[i],NULL,pth_start,(void*)i);
 34         if(ret!=0){
 35             printf("yellowbull not exist!");
 36             return -1;
 37         }
 38     }
 39     //退出线程
 40     for(i=0;i<4;i++){
 41         pthread_join(tid[i],NULL);
 42     }
 43     //销毁互斥锁变量
 44     pthread_mutex_destroy(&mutex);
 45     return 0;
 46 }
 

在这里就用到了互斥锁变量,如果不加互斥锁的话,会出现多个黄牛抢了同一张票,这显然是不合理的,所以在访问临界资源时要加锁,这样每次就是一个黄牛在访问临界资源(抢票),在访问完毕后要解锁,如果没有加完锁后发现没票了,也是要解锁的,否则会导致后面的黄牛死等。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值