美文网首页
第四篇:加锁解决资源竞争问题

第四篇:加锁解决资源竞争问题

作者: 意一ineyee | 来源:发表于2017-10-12 14:29 被阅读35次

目录

一、资源竞争及加锁概述
二、三种加锁方式
 1、@synchronized
 2、NSLock
 3、GCD信号量

一、资源竞争及加锁概述


通常情况下我们使用多线程开发往往是多个线程并发执行的,这时一个资源就可能被多个线程同时访问,造成资源竞争,这个过程中如果不对该资源的访问加一下锁,就会导致数据安全问题。

我们先举个“买票”的例子来说明多线程是如何导致资源竞争造成数据安全问题的。

@interface ViewController ()

@property (assign,    atomic) int totalTicketCount;// 总票数

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 假设有两个窗口在卖票, 一共还剩下 6 张票, 第一个窗口排了 5 个人, 第二个窗口排了 4 个人
    self.totalTicketCount = 6;
    
    // 第一个窗口 --> 第一个线程
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        
        for (int i = 0; i < 5; i ++) {
            
            [self buyTicket];
        }
    });
    
    // 第二个窗口 --> 第二个线程
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        
        for (int i = 0; i < 4; i ++) {
            
            [self buyTicket];
        }
    });
}

- (void)buyTicket {
    
    if (self.totalTicketCount <= 0) {
        
        NSLog(@"%@===票没了, 明天再来吧", [NSThread currentThread]);
        return;
    }
    NSLog(@"%@===剩余票数为 : %d", [NSThread currentThread], self.totalTicketCount);
    self.totalTicketCount --;// 卖一张
}

输出 :

<NSThread: 0x604000467b80>{number = 4, name = (null)}===剩余票数为 : 6
<NSThread: 0x6000004766c0>{number = 3, name = (null)}===剩余票数为 : 6
<NSThread: 0x6000004766c0>{number = 3, name = (null)}===剩余票数为 : 4
<NSThread: 0x604000467b80>{number = 4, name = (null)}===剩余票数为 : 5
<NSThread: 0x604000467b80>{number = 4, name = (null)}===剩余票数为 : 2
<NSThread: 0x6000004766c0>{number = 3, name = (null)}===剩余票数为 : 2
<NSThread: 0x604000467b80>{number = 4, name = (null)}===剩余票数为 : 1
<NSThread: 0x6000004766c0>{number = 3, name = (null)}===票没了, 明天再来吧
<NSThread: 0x6000004766c0>{number = 3, name = (null)}===票没了, 明天再来吧

可见:

数据出问题了。

分析一下:

比如现在有6张票,如果不给资源加锁,那么两个线程是都可以进来访问这个票数的,假设第一个线程先进来了,判断完之后它在做减1操作但是还没来得及减,第二个线程就进来做判断了,那么因为第一个线程还没有完成减1操作,所以第二个线程做判断的时候用的还是6做判断,但其实第一个线程已经在作减1操作,第二个线程应该是用5来做判断的,就是线程一没减完线程二就进来了,所以造成了数据出问题。

因此,为了避免这种多个线程资源竞争导致的数据安全问题,我们就需要给资源访问代码加锁来保证同一时间只有一个线程能访问资源。iOS中我们常用的资源加锁方式有三种:@synchronized, NSLock和GCD信号量

二、三种加锁方式


1、@synchronized

使用方法:@synchronized(self) {加锁代码}

我们将上面的buyTicket方法修改一下:

- (void)buyTicket {
    
    @synchronized(self) {
        
        if (self.totalTicketCount <= 0) {
            
            NSLog(@"%@===票没了, 明天再来吧", [NSThread currentThread]);
            return;
        }
        NSLog(@"%@===剩余票数为 : %d", [NSThread currentThread], self.totalTicketCount);
        self.totalTicketCount --;// 卖一张
    }
}
2、NSLock

使用方法:[self.lock lock]; {加锁代码} [self.lock unlock];

我们将上面的buyTicket方法修改一下(自己创建一个lock属性):

- (void)buyTicket {
    
    [self.lock lock];
        
    if (self.totalTicketCount <= 0) {
        
        NSLog(@"%@===票没了, 明天再来吧", [NSThread currentThread]);
        return;
    }
    NSLog(@"%@===剩余票数为 : %d", [NSThread currentThread], self.totalTicketCount);
    self.totalTicketCount --;// 卖一张
    
    [self.lock unlock];
}
3、GCD信号量

使用GCD信号量实现加锁的原理:主要是利用信号的通知信号等待信号来实现的,即每当我们发送一个通知信号时,信号的信号量会+1;每当我们发送一个等待信号时,信号的信号量会-1;而如果信号的信号量为0,这个信号会进入等待状态,使得其它线程无法执行,直到等待过程中我们发出了通知信号使得信号的信号量+1了,其它线程才能继续执行。因此利用这个原理我们可以初始化一个GCD信号并且默认信号量为1,在加锁代码前发送一个等待信号,让信号量-1减为0,信号开始等待,使得其它线程处于等待状态无法进入,执行完后再发送一个通知信号,让信号量+1加为1,让其他线程释放等待,继续执行加锁代码。

我们可以修改代码为:

@interface ViewController ()

@property (strong, nonatomic) dispatch_semaphore_t semaphore;// GCD 信号量
@property (assign,    atomic) int totalTicketCount;// 总票数

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // GCD 信号量, 并设置信号量的默认值为 1
    self.semaphore = dispatch_semaphore_create(1);
    
    // 假设有两个窗口在卖票, 一共还剩下 6 张票, 第一个窗口排了 5 个人, 第二个窗口排了 4 个人
    self.totalTicketCount = 6;
    
    // 第一个窗口 --> 第一个线程
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        
        for (int i = 0; i < 5; i ++) {
            
            [self buyTicket];
        }
    });
    
    // 第二个窗口 --> 第二个线程
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        
        for (int i = 0; i < 4; i ++) {
            
            [self buyTicket];
        }
    });
}

- (void)buyTicket {
    
    // 发送等待信号, 发现信号量为 1, 执行下面的代码, 并将信号量 -1, 减为 0 --> 信号进入等待状态, 其它线程无法进入
    dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
        
    if (self.totalTicketCount <= 0) {
        
        NSLog(@"%@===票没了, 明天再来吧", [NSThread currentThread]);
        return;
    }
    NSLog(@"%@===剩余票数为 : %d", [NSThread currentThread], self.totalTicketCount);
    self.totalTicketCount --;// 卖一张
    
    // 发送通知信号, 信号量 +1, 加为 1 --> 信号释放等待状态, 其它线程继续执行加锁代码
    dispatch_semaphore_signal(self.semaphore);
}

@end
4、三种加锁方式的对比

@synchronized、NSLock和使用GCD信号实现资源加锁这三种方式在使用便捷度上依次递减的,但是性能却是依次递增的。

5、给资源加锁时候我们需要注意什么
  • 给资源加锁是因为有的场景确实需要通过资源加锁来保证数据安全,但并不是什么情况都要加锁的,因为加锁会保证只有一个线程在执行,这就失去了当初开辟多线程的意义。

  • 资源加锁的最终原理都是使得一个线程在访问某个资源时,其它的线程被阻塞,直到前一个线程访问完,后一个线程的阻塞才会被释放从而访问资源。因此加锁代码应该仅仅是对被抢占资源的读取或者修改的代码,而不应该无脑地将过多的代码放到锁里面,否则一个线程执行的时候另一个线程就得等很长时间,等都等死了。

  • 我们虽然通过加锁在这个地方(同一个地方)保证了多个线程不能通过这段代码同时访问资源,但是我们不能保证别的地方没有别的代码在读取或者修改资源呀,因此我们最好是把资源用atomic来修饰,因为atomic执行读取的是寄存器的数据,而nonatomic读取的是内存数据,这样一来就可以保证不同地方的多个线程也可以访问到正确的资源。

相关文章

  • 第四篇:加锁解决资源竞争问题

    目录一、资源竞争及加锁概述二、三种加锁方式 1、@synchronized 2、NSLock 3、GCD信号量 一...

  • 【Golang】通道channel

    Java的并发:基于线程Golang的并发:基于协程goroutine 并发会导致资源竞争:加锁防止资源竞争的三种...

  • 分布式锁 - redis实现方案

    在普通的单机程序中,我们为了避免资源竞争,通常会使用synchronize、lock 等方式进行加锁防止并发问题。...

  • 对于加锁的一些思考

    对于资源加锁的一些思考 本文主要介绍"微信抢票"与“THU琴房预约”项目中处理资源访问竞争问题的一些对比与思考。 ...

  • 20170402万众创业

    现代社会可以更加公平竞争,有机会能把握,遇到问题能通过解决资源解决。

  • 浅谈:OC都有哪些锁机制

    1:NSLock 对于资源抢占问题我们可以考虑使用同步锁NALock来解决,使用时把需要加锁的代码放到NSLock...

  • 做开发,这几种锁机制你不得不了解一下

    摘要:并发访问共享资源,如果不加锁,可能会导致数据不一致问题,通常为了解决并发访问问题,我们都会在访问共享资源之前...

  • java设计模式 — 单例模式

    常见模式 问题:使用没使用都会产生实例 懒汉模式 问题:存在线程问题,解决思路加锁 问题:由于加锁性能会下降 静态...

  • 一文看懂Mysql中的常用锁

    Mysql中的锁 锁机制是用来解决资源争用的常用手段。对某个粒度的资源加锁,访问资源资源需要先得到锁。 Mysql...

  • (202)Lock实现原理

    当多个线程需要访问某个公共资源的时候,我们知道需要通过加锁来保证资源的访问不会出问题。java提供了两种方式来加锁...

网友评论

      本文标题:第四篇:加锁解决资源竞争问题

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