美文网首页
iOS 多线程(二)关于线程安全问题

iOS 多线程(二)关于线程安全问题

作者: shuaikun | 来源:发表于2020-12-04 10:16 被阅读0次

一、什么是死锁?

  • 定义:如果一个进程集合里面的每个线程都在等待这个集合中的其他一个线程(包括自身)才能继续往下执行,若无外力他们将无法推进,这种情况就是死锁,处于死锁状态的进程称为死锁进程
  • 举例:两个线程A和B都卡住了,并等待对方完成某些操作。A不能完成是因为它在等待B完成。但B也不能完成,因为它在等待A完成。于是大家都完不成,就导致了死锁(DeadLock)。

二、死锁产生的原因?

1、因竞争资源发生死锁现象:

  系统中供多个进程共享的资源的数目不足以满足全部进程的需要时,就会引起对诸资源的竞争而发生死锁现象
 (1)可剥夺资源和不可剥夺资源:
  可剥夺资源是指某进程在获得该类资源时,该资源同样可以被其他进程或系统剥夺,不可剥夺资源是指当系统把该类资源分配给某个进程时,不能强制收回,只能在该进程使用完成后自动释放
 (2)竞争不可剥夺资源:
  系统中不可剥夺资源的数目不足以满足诸进程运行的要求,则发生在运行进程中,不同的进程因争夺这些资源陷入僵局。

举例说明:

  • 资源:A,B
  • 进程:C,D
  • 资源A,B都是不可剥夺资源:一个进程申请了之后,不能强制收回,只能进程结束之后自动释放,内存就是可剥夺资源。
  • 进程C申请了资源A,进程D申请了资源B。
  • 接下来C的操作用到资源B,D的资源用到资源A。由于此时D占有B资源不释放等待A资源,C占有A资源等待B资源,并且CD资源都不释放已经占有的资源。所以,C D都得不到接下来的资源,那么就引发了死锁。

(3)竞争临时资源

2.进程推进顺序不当发生死锁

三、产生死锁的四个必要条件?

image.jpeg
1、互斥条件:

  一个资源每次只能被一个进程使用,进程对所分配到的资源不允许其他进程进行访问,若其他进程访问该资源,只能等待,直至占有该资源的进程使用完成后释放该资源

2、请求和保持条件:

  一个进程因请求资源而阻塞时,对已获得的资源保持不放。进程获得一定的资源之后,又对其他资源发出请求,但是该资源可能被其他进程占有,此事请求阻塞,但又对自己获得的资源保持不放

3. 不可剥夺条件:

  是指进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用完后自己释放,若干进程之间形成一种头尾相接的循环等待资源关系。

4. 循环等待条件:

  是指进程发生死锁后,必然存在一个进程--资源之间的环形链,若干进程之间形成一种头尾相接的循环等待资源关系。

这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。

四、处理死锁的基本方法

1、预防死锁:

  通过设置一些限制条件,去破坏产生死锁的必要条件

2、避免死锁:

  在资源分配过程中,使用某种方法避免系统进入不安全的状态,从而避免发生死锁

3、检测死锁:

  允许死锁的发生,但是通过系统的检测之后,采取一些措施,将死锁清除掉

4、解除死锁:

  该方法与检测死锁配合使用

五、银行家算法

1. 银行家算法是一种最有代表性的避免死锁的算法。在避免死锁方法中允许进程动态地申请资源,但系统在进行资源分配之前,应先计算此次分配资源的安全性,若分配不会导致系统进入不安全状态,则分配,否则等待。

(1)每个进程所能获取的每种资源数量是多少[MAX]
(2)每个进程当前所分配到的每种资源的数量是多少[ALLOCATED]
(3)系统当前可分配的每种的资源数量是多少[AVAILABLE]
(4)对于银行家算法的实现,需要知道三件事: 只有当资源满足以下条件,资源才会被分配

  • request <= max, 也可设置错误条件,当进程所请求的资源超过最大的要求
  • request <= available, 或者进程一直等直到资源可分配
2、按照银行家算法的思想,当进程请求资源时,系统将按照如下原则分配系统资源

(1)当一个进程对资源的最大需求量不超过系统中的资源数时可以接纳该进程。
(2) 进程可以分期请求资源,当请求的总数不能超过最大需求量。
(3) 当系统现有的资源不能满足进程尚需资源数时,对进程的请求可以推迟分配,但总能使进程在有限的时间里得到资源。
(4) 当系统现有的资源能满足进程尚需资源数时,必须测试系统现存的资源能否满足该进程尚需的最大资源数,若能满足则按当前的申请量分配资源,否则也要推迟分配。

六、举例

死锁就是队列引起的循环等待

1、同样,下边的代码也会造成死锁:
- (void)viewDidLoad {
   [super viewDidLoad]; 
   dispatch_sync(dispatch_get_main_queue(), ^{
         NSLog(@"deallock");
   }); // Do any additional setup after loading the view, typically from a nib.
 }

对于以上问题:
  (1)在主线程中运用主队列同步,也就是把任务放到了主线程的队列中。
  (2) 同步对于任务是立刻执行的,那么当把任务放进主队列时,它就会立马执行,只有执行完这个任务,viewDidLoad才会继续向下执行。
  (3)而viewDidLoad和任务都是在主队列上的,由于队列的先进先出原则,任务又需等待viewDidLoad执行完毕后才能继续执行,viewDidLoad和这个任务就形成了相互循环等待,就造成了死锁。
  (4)想避免这种死锁,可以将同步改成异步dispatch_async,或者将dispatch_get_main_queue换成其他串行或并行队列,都可以解决。

2、同样,下边的代码也会造成死锁:
dispatch_queue_t serialQueue = dispatch_queue_create("test",DISPATCH_QUEUE_SERIAL); 
dispatch_async(serialQueue, ^{ 
  dispatch_sync(serialQueue, ^{
      NSLog(@"deadlock");
  });
 });

  外面的队列无论是同步还是异步都会造成死锁,其原因跟上面是一样的。这是因为里面的任务和外面的任务都在同一个serialQueue队列内,又是同步,这就和上边主队列同步的例子一样造成了死锁 解决方法也和上边一样,将里面的同步改成异步dispatch_asyn,或者将serialQueue换成其他串行或并行队列,都可以解决

dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL); 
dispatch_queue_t serialQueue2 = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL); 
dispatch_async(serialQueue, ^{ 
  dispatch_sync(serialQueue2, ^{
      NSLog(@"deadlock"); 
  });
});

  这样是不会死锁的,并且serialQueueserialQueue2是在同一个线程中的。

相关文章

网友评论

      本文标题:iOS 多线程(二)关于线程安全问题

      本文链接:https://www.haomeiwen.com/subject/glddwktx.html