如何保证iOS的多线程安全

什么会给多线程的安全造成隐患?

有了多线程技术支持,我们可以并发的进行多个任务,因此同一块资源就有可能在多个线程中同时被访问(读/写)。这个现象叫作资源共享,比如多个线程同时访问了同一个对象,同一个变量或同一个文件,这样就有可能引发数据错乱何数据安全问题。

经典问题一——存钱取钱存钱取钱问题下面通过代码展示一下该问题

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
   
    [self moneyTest];
}

//存钱取钱测试
-(void)moneyTest {
   
    self.money = 100;
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_async(queue, ^{
   
        for (int i= 0; i<10; i++) {
   
            [self saveMoney];
        }
        
    });
    dispatch_async(queue, ^{
   
        for (int i = 0; i<10; i++) {
   
            [self drawMoney];
        }
    });
}

-(void)saveMoney {
   
    NSInteger oldMoney = self.money;
    sleep(.2);//模拟任务时长,便于问题显现
    oldMoney += 50;
    self.money = oldMoney;
    NSLog(@"存了50元,账户余额%ld-------%@",(long)oldMoney, [NSThread currentThread]);
}

-(void)drawMoney {
   
    NSInteger oldMoney = self.money;
    sleep(.2);//模拟任务时长,便于问题显现
    oldMoney -= 20;
    self.money = oldMoney;
    NSLog(@"取了20元,账户余额%ld-------%@",(long)oldMoney, [NSThread currentThread]);
}

我们在moneyTest方法中,以多线程方式分别进行了10次的存钱取钱操作,每次存50,每次取20,存款初值为100,目标余额应该为100+ (50*10) - (20*10) = 400,运行结果如下多线程存钱取钱问题
可以看出最后的余额数不对。


经典问题二——卖票问题卖票问题下面通过代码展示一下该问题

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
   
    [self sellTicketTest];
}

//卖票问题
-(void)sellTicketTest {
   
    self.ticketsCount = 30;
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_async(queue, ^{
   
        for (int i= 0; i<5; i++) {
   
            [self sellTicket];
        }
        
    });
    dispatch_async(queue, ^{
   
        for (int i = 0; i<5; i++) {
   
            [self sellTicket];
        }
    });
    dispatch_async(queue, ^{
   
        for (int i = 0; i<5; i++) {
   
            [self sellTicket];
        }
    });
}

-(void)sellTicket {
   
    NSInteger oldTicketsCount = self.ticketsCount;
    sleep(.2);//模拟任务时长,便于问题显现
    oldTicketsCount--;
    self.ticketsCount = oldTicketsCount;
    NSLog(@"还剩%ld张票-------%@",(long)oldTicketsCount, [NSThread currentThread]);
}

sellTicketTest里面,起始票数30,通过3条线程同时卖票,每条线程卖10张,最有应该全部卖完才对,运行程序,结果如下多线程卖票问题
打印结果看出最后的剩余票数发生了错误。

上述两个经典问题都是由于多条线程对同一资源进行了读写操作而导致的。用一个大家都熟悉的图片来表示就是

针对这个问题的解决方案:使用线程同步技术(同步,就是协同步调,按预定的先后次序进行)。常见的线程同步技术就是:加锁
先用下图概括一下线程加锁


线程同步(加锁)方案

iOS中的线程同步方案有如下几种:

  • OSSpinLock
  • os_unfair_lock
  • pthread_mutex
  • dispatch_semaphore
  • dispatch_queue(DISPATCH_QUEUE_SERIAL)
  • NSLock
  • NSRecursiveLock
  • NSCondition
  • NSConditionLock
  • @synchronized

下面我们来依次体验一下。

(一)OSSpinLock
OSSpinLock叫作”自旋锁“,需要导入头文件#import <libkern/OSAtomic.h>。它有如下API

  • OSSpinLock lock = OS_UNFAIR_LOCK_INIT; ——初始化锁对象lock
  • OSSpinLockTry(&lock);——尝试加锁,加锁成功继续,加锁失败返回,继续执行后面的代码,不阻塞线程
  • OSSpinLockLock(&lock);——加锁,加锁失败会阻塞线程,进行等待
  • OSSpinLockUnlock(&lock);——解锁

下面来看一下代码中如何使用它。以下代码承接上面的卖票案例进行加锁操作

@interface ViewController ()
@property (nonatomic, assign) NSInteger ticketsCount;
@end

*******************************************************

@implementation ViewController
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
   
    [self sellTicketTest];
//    [self moneyTest];
}
//卖票问题
-(void)sellTicketTest {
   
    self.ticketsCount = 30;
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_async(queue, ^{
   
        for (int i= 0; i<10; i++) {
   
            [self sellTicket];
        }
        
    });
    dispatch_async(queue, ^{
   
        for (int i = 0; i<10; i++) {
   
            [self sellTicket];
        }
    });
    dispatch_async(queue, ^{
   
        for (int i = 0; i<10; i++) {
   
            [self sellTicket];
        }
    });
}

-(void)sellTicket {
   
    //初始化锁
    OSSpinLock lock = OS_SPINLOCK_INIT;
    //加锁????
    OSSpinLockLock(&lock);
    
    
    NSInteger oldTicketsCount = self.ticketsCount;
    sleep(.2);//模拟任务时长,便于问题显现
    oldTicketsCount--;
    self.ticketsCount = oldTicketsCount;
    NSLog(@"还剩%ld张票-------%@",(long)oldTicketsCount, [NSThread currentThread]);
    
    //解锁????
    OSSpinLockUnlock(&lock);
}
@end

运行后结果结果看没成功,怎么回事呢?这里补充一下加锁的原理:一张图搞定加锁原理

所以根据上图的原理,我们应该使用同一个锁对象来给某个操作(代码段)加锁。所以将上面的锁写成一个全局性质的属性即可,代码如下

@interface ViewController ()
@property (nonatomic, assign) NSInteger ticketsCount;
@property (nonatomic, assign) OSSpinLock lock;
@end
*******************************************************
@implementation ViewController

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
   
    [self sellTicketTest];
}

//卖票问题
-(void)sellTicketTest {
   
    self.ticketsCount = 30;
    //初始化锁
    self.lock = OS_SPINLOCK_INIT;
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_async(queue, ^{
   
        for (int i= 0; i<10; i++) {
   
            [self sellTicket];
        }
        
    });
    dispatch_async(queue, ^{
   
        for (int i = 0; i<10; i++) {
   
            [self sellTicket];
        }
    });
    dispatch_async(queue, ^{
   
        for (int i = 0; i<10; i++) {
   
            [self sellTicket];
        }
    });
}

-(void)sellTicket {
   
    
    //加锁????
    OSSpinLockLock(&_lock);
    
    
    NSInteger oldTicketsCount = self.ticketsCount;
    sleep(.2);//模拟任务时长,便于问题显现
    oldTicketsCount--;
    self.ticketsCount = oldTicketsCount;
    NSLog(@"还剩%ld张票-------%@",(long)oldTicketsCount, [NSThread currentThread]);
    
    //解锁????
    OSSpinLockUnlock(
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值