美文网首页
iOS多线程-安全篇

iOS多线程-安全篇

作者: Jey | 来源:发表于2018-12-05 10:50 被阅读11次

    多个线程访问同一块资源的时候,很容易引发数据混乱问题。
    看下面的例子,3个线程分别买票7次,看下面打印结果

    - (void)ticketTest{
        self.ticketsCount = 21;
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        
        for (NSInteger i = 0; i < 3; i++) {
            dispatch_async(queue, ^{
                for (int i = 0; i < 7; i++) {
                    [self sellingTickets];
                }
            });
        }
        
    }
    //卖票
    - (void)sellingTickets{
        int oldMoney = self.ticketsCount;
        sleep(.2);
        oldMoney -= 1;
        self.ticketsCount = oldMoney;
        
        NSLog(@"当前剩余票数-> %d", oldMoney);
    }
    
    输出:
    2018-12-05 10:01:18.630619+0800 001--Block[7762:559583] 当前剩余票数-> 20
    2018-12-05 10:01:18.630619+0800 001--Block[7762:559582] 当前剩余票数-> 20
    2018-12-05 10:01:18.630660+0800 001--Block[7762:559581] 当前剩余票数-> 20
    2018-12-05 10:01:18.630853+0800 001--Block[7762:559583] 当前剩余票数-> 19
    2018-12-05 10:01:18.630853+0800 001--Block[7762:559582] 当前剩余票数-> 19
    2018-12-05 10:01:18.631212+0800 001--Block[7762:559581] 当前剩余票数-> 18
    2018-12-05 10:01:18.631220+0800 001--Block[7762:559583] 当前剩余票数-> 17
    2018-12-05 10:01:18.631491+0800 001--Block[7762:559582] 当前剩余票数-> 16
    2018-12-05 10:01:18.631606+0800 001--Block[7762:559581] 当前剩余票数-> 15
    2018-12-05 10:01:18.632321+0800 001--Block[7762:559583] 当前剩余票数-> 14
    2018-12-05 10:01:18.632695+0800 001--Block[7762:559582] 当前剩余票数-> 13
    2018-12-05 10:01:18.633174+0800 001--Block[7762:559581] 当前剩余票数-> 12
    2018-12-05 10:01:18.633746+0800 001--Block[7762:559583] 当前剩余票数-> 11
    2018-12-05 10:01:18.634030+0800 001--Block[7762:559582] 当前剩余票数-> 10
    2018-12-05 10:01:18.634240+0800 001--Block[7762:559581] 当前剩余票数-> 9
    2018-12-05 10:01:18.635149+0800 001--Block[7762:559583] 当前剩余票数-> 8
    2018-12-05 10:01:18.635715+0800 001--Block[7762:559582] 当前剩余票数-> 7
    2018-12-05 10:01:18.636263+0800 001--Block[7762:559581] 当前剩余票数-> 6
    2018-12-05 10:01:18.637100+0800 001--Block[7762:559583] 当前剩余票数-> 5
    2018-12-05 10:01:18.637324+0800 001--Block[7762:559582] 当前剩余票数-> 4
    2018-12-05 10:01:18.637685+0800 001--Block[7762:559581] 当前剩余票数-> 3
    

    正常情况下我有21张票,然后卖了21次,剩余票数应该是0,但是打印结果竟然是3,所以这里就存在了线程安全问题。

    出现线程安全的原因就是在同一个时间,多个线程同时读取一个值,像线程A和B同时读取了当前票数为10,等于是卖了两张票,但是总票数其实就减少了一张。

    解决方法

    线程同步方法,加锁。

    两种锁的加锁原理

    互斥锁:线程会从sleep(加锁)——>running(解锁),过程中有上下文的切换,cpu的抢占,信号的发送等开销。

    自旋锁:线程一直是running(加锁——>解锁),死循环检测锁的标志位,机制不复杂。

    对比 互斥锁的起始原始开销要高于自旋锁,但是基本是一劳永逸,临界区持锁时间的大小并不会对互斥锁的开销造成影响,而自旋锁是死循环检测,加锁全程消耗cpu,起始开销虽然低于互斥锁,但是随着持锁时间,加锁的开销是线性增长。

    两种锁的应用

    互斥锁用于临界区持锁时间比较长的操作,比如下面这些情况都可以考虑

    1 临界区有IO操作
    2 临界区代码复杂或者循环量大
    3 临界区竞争非常激烈
    4 单核处理器
    至于自旋锁就主要用在临界区持锁时间非常短且CPU资源不紧张的情况下,自旋锁一般用于多核的服务器。

    1、OSSpinLock自旋锁的使用

    OSSpinLock叫做”自旋锁”,使用时需要导入头文件#import <libkern/OSAtomic.h>

    //  OSSpinLock自旋锁的初始化
    OSSpinLock _lock = OS_SPINLOCK_INIT;
    
    //  锁定
    OSSpinLockLock(&_lock);
    
    // 解锁
    OSSpinLockUnlock(&_lock);
    

    这样我们就可以把上面买票代码加锁了

    - (void)ticketTest{
        _ticketLock = OS_SPINLOCK_INIT;
        self.ticketsCount = 21;
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        
        for (NSInteger i = 0; i < 3; i++) {
            dispatch_async(queue, ^{
                for (int i = 0; i < 7; i++) {
                    [self sellingTickets];
                }
            });
        }
        
    }
    //卖票
    - (void)sellingTickets{
        OSSpinLockLock(&_ticketLock);
        int oldMoney = self.ticketsCount;
        sleep(.2);
        oldMoney -= 1;
        self.ticketsCount = oldMoney;
        OSSpinLockUnlock(&_ticketLock);
        NSLog(@"当前剩余票数-> %d", oldMoney);
    }
    

    这样就可以保证票数卖完,数量同步了
    OSSpinLock在iOS10.0以后就被弃用了,可以使用os_unfair_lock_lock替代。而且还有一些安全性问题,具体参考不再安全的 OSSpinLock

    2、os_unfair_lock

    os_unfair_lock用于取代不安全的OSSpinLock ,从iOS10开始才支持 从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态,并非忙等 需要导入头文件#import <os/lock.h>

    //初始化
    os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
    //加锁
    os_unfair_lock_lock(&lock);
    //解锁
    os_unfair_lock_unlock(&lock);
    
    3、pthread_mutex

    mutex叫做”互斥锁”,等待锁的线程会处于休眠状态。需要导入头文件#import <pthread.h> 使用步骤

    4、NSLock

    NSLock是对mutex普通锁的封装。pthread_mutex_init(mutex, NULL);

    NSLock 遵循 NSLocking 协议。Lock 方法是加锁,unlock 是解锁,tryLock 是尝试加锁,如果失败的话返回 NO,lockBeforeDate: 是在指定Date之前尝试加锁,如果在指定时间之前都不能加锁,则返回NO

    #import "LockDemo.h"
    @interface LockDemo()
    @property (strong, nonatomic) NSLock *ticketLock;
    @end
    @implementation LockDemo
    //卖票
    - (void)sellingTickets{
    [self.ticketLock lock];
    [super sellingTickets];
    [self.ticketLock unlock];
    }
    
    @end
    

    相关文章

      网友评论

          本文标题:iOS多线程-安全篇

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