美文网首页
iOS-常见锁的类型及用法

iOS-常见锁的类型及用法

作者: 翀鹰精灵 | 来源:发表于2019-12-29 20:50 被阅读0次

    说起“锁”,一般都要涉及多线程,因为一块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源。比如:多个线程访问同一个对象、同一个变量、同一个文件。当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题。这篇文章中,我将会整理一下 iOS 开发中几种多线程方案。
    首先,我们来了解一下,iOS中常见的多线程方案:

    一、iOS中常见的多线程方案有四种:

    pthread : 纯粹 C 语言的API,跨平台, 线程生命周期程序员管理,几乎不用。
    NSThread: OC 面向对象 API ,简单易用,可以直接操作线程对象, 线程生命周期程序员管理,偶尔使用。
    GCD:旨在替代NSThread,充分利用 设备多核,C 语言的API,线程生命周期自动管理 ,经常使用。
    NSOPeration:基于 GCD (底层就是GCD),比GCD多了些实用功能,OC 方法,自动管理线程,经常实用。

    二、GCD简单介绍:

    GCD中核心有四个概念:同步、异步、串行、并发
    同步执行函数:dispatch_sync();
    异步执行函数:dispatch_async();
    串行队列: dispatch_queue_t queue = dispatch_get_main_queue();(主队列 是一种特殊的串行队列
    并发队列: ①全局的并发队列: dispatch_quque_t queue = dispatch_get_global_queue(0,0);
    ② 手动创建并发队列: dispatch_queue_t queue = dispatch_queue_create("myQueue",DISPATCH_QUEUE_CONCURRENT);

    这里,我就不过多的阐述什么是同步、什么是异步、什么是串行、什么是并行等问题,需要了解的可自行查阅。下面我们来介绍本文的核心

    三、多线程-安全隐患示例

    开篇说过,当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题。这里有一个经典的例子,火车票卖票和存取取钱的例子,我们分别展开来说一下:
    例子一:卖火车票

    卖票代码如下:

    #import "ViewController.h"
    
    @interface ViewController ()
    @property (assign, nonatomic) int ticketsCount;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        [self ticketTestAction];
    }
    /// 卖票演示
    - (void)ticketTestAction {
        self.ticketsCount = 15;
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        dispatch_async(queue, ^{
            for (int i = 0; i < 5; i++) {
                 [self saleSingleTicket];
            }
        });
        dispatch_async(queue, ^{
            for (int i = 0; i < 5; i++) {
                [self saleSingleTicket];
            }
        });
        dispatch_async(queue, ^{
            for (int i = 0; i < 5; i++) {
                [self saleSingleTicket];
            }
        });
    }
    
    /// 卖票事件
    - (void)saleSingleTicket {
        int oldTicketsCount = self.ticketsCount;
        sleep(.2);
        oldTicketsCount--;
        self.ticketsCount = oldTicketsCount;
        
        NSLog(@"还剩%d张票 - %@", oldTicketsCount, [NSThread currentThread]);
    }
    @end
    

    打印结果如下:

    2019-12-29 15:24:07.794256+0800 GCDDemo[4974:135412] 还剩12张票 - <NSThread: 0x600002a13040>{number = 5, name = (null)}
    2019-12-29 15:24:07.794260+0800 GCDDemo[4974:135410] 还剩14张票 - <NSThread: 0x600002a6cc00>{number = 6, name = (null)}
    2019-12-29 15:24:07.794317+0800 GCDDemo[4974:135416] 还剩13张票 - <NSThread: 0x600002a68880>{number = 7, name = (null)}
    2019-12-29 15:24:07.794442+0800 GCDDemo[4974:135412] 还剩11张票 - <NSThread: 0x600002a13040>{number = 5, name = (null)}
    2019-12-29 15:24:07.794471+0800 GCDDemo[4974:135410] 还剩10张票 - <NSThread: 0x600002a6cc00>{number = 6, name = (null)}
    2019-12-29 15:24:07.794620+0800 GCDDemo[4974:135416] 还剩9张票 - <NSThread: 0x600002a68880>{number = 7, name = (null)}
    2019-12-29 15:24:07.794703+0800 GCDDemo[4974:135410] 还剩8张票 - <NSThread: 0x600002a6cc00>{number = 6, name = (null)}
    2019-12-29 15:24:07.794845+0800 GCDDemo[4974:135412] 还剩7张票 - <NSThread: 0x600002a13040>{number = 5, name = (null)}
    2019-12-29 15:24:07.794889+0800 GCDDemo[4974:135410] 还剩6张票 - <NSThread: 0x600002a6cc00>{number = 6, name = (null)}
    2019-12-29 15:24:07.794981+0800 GCDDemo[4974:135416] 还剩5张票 - <NSThread: 0x600002a68880>{number = 7, name = (null)}
    2019-12-29 15:24:07.795366+0800 GCDDemo[4974:135412] 还剩4张票 - <NSThread: 0x600002a13040>{number = 5, name = (null)}
    2019-12-29 15:24:07.798222+0800 GCDDemo[4974:135416] 还剩3张票 - <NSThread: 0x600002a68880>{number = 7, name = (null)}
    2019-12-29 15:24:07.798612+0800 GCDDemo[4974:135412] 还剩2张票 - <NSThread: 0x600002a13040>{number = 5, name = (null)}
    2019-12-29 15:24:07.799321+0800 GCDDemo[4974:135416] 还剩1张票 - <NSThread: 0x600002a68880>{number = 7, name = (null)}
    2019-12-29 15:24:07.800157+0800 GCDDemo[4974:135410] 还剩0张票 - <NSThread: 0x600002a6cc00>{number = 6, name = (null)}
    

    眨一看,貌似没什么问题,但是细看下,发现剩余票数的计算确实存在问题,刚上来还只剩余了12张,但是又卖了一张,就剩余14张了,出现了越卖越多的情况,这显然是不合理的,这就是多线程出现的问题。

    例子二 、存钱取钱

    存钱取钱代码如下:

    
    #import "ViewController.h"
    
    @interface ViewController ()
    @property (assign, nonatomic) int ticketsCount;
    @property (assign, nonatomic) int moneyCount;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        [self moneyTest];
    }
    ///  存钱、取钱演示
    - (void)moneyTest {
        self.moneyCount = 50;
        
        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 {
        int oldMoney = self.moneyCount;
        sleep(.2);
        oldMoney += 100;
        self.moneyCount = oldMoney;
        
        NSLog(@"存100,还剩%d元 - %@", oldMoney, [NSThread currentThread]);
    }
    
    ///  取钱
    - (void)drawMoney {
        int oldMoney = self.moneyCount;
        sleep(.2);
        oldMoney -= 50;
        self.moneyCount = oldMoney;
        
        NSLog(@"取50,还剩%d元 - %@", oldMoney, [NSThread currentThread]);
    }
    
    @end
    

    打印结果如下:

    2019-12-29 15:32:59.075956+0800 GCDDemo[5180:155847] 取50,还剩0元 - <NSThread: 0x6000035b2e40>{number = 5, name = (null)}
    2019-12-29 15:32:59.075962+0800 GCDDemo[5180:155845] 存100,还剩100元 - <NSThread: 0x6000035df400>{number = 4, name = (null)}
    2019-12-29 15:32:59.076154+0800 GCDDemo[5180:155847] 取50,还剩50元 - <NSThread: 0x6000035b2e40>{number = 5, name = (null)}
    2019-12-29 15:32:59.076147+0800 GCDDemo[5180:155845] 存100,还剩200元 - <NSThread: 0x6000035df400>{number = 4, name = (null)}
    2019-12-29 15:32:59.076331+0800 GCDDemo[5180:155847] 取50,还剩150元 - <NSThread: 0x6000035b2e40>{number = 5, name = (null)}
    2019-12-29 15:32:59.076496+0800 GCDDemo[5180:155847] 取50,还剩100元 - <NSThread: 0x6000035b2e40>{number = 5, name = (null)}
    2019-12-29 15:32:59.076628+0800 GCDDemo[5180:155847] 取50,还剩150元 - <NSThread: 0x6000035b2e40>{number = 5, name = (null)}
    2019-12-29 15:32:59.076574+0800 GCDDemo[5180:155845] 存100,还剩200元 - <NSThread: 0x6000035df400>{number = 4, name = (null)}
    2019-12-29 15:32:59.076817+0800 GCDDemo[5180:155847] 取50,还剩100元 - <NSThread: 0x6000035b2e40>{number = 5, name = (null)}
    2019-12-29 15:32:59.077335+0800 GCDDemo[5180:155845] 存100,还剩200元 - <NSThread: 0x6000035df400>{number = 2019-12-29 15:32:59.077901+0800 GCDDemo[5180:155847] 取50,还剩150元 - <NSThread: 0x6000035b2e40>{number = 5, name = (null)}
    4, name = (null)}
    2019-12-29 15:32:59.078920+0800 GCDDemo[5180:155847] 取50,还剩100元 - <NSThread: 0x6000035b2e40>{number = 5, name = (null)}
    2019-12-29 15:32:59.079308+0800 GCDDemo[5180:155845] 存100,还剩200元 - <NSThread: 0x6000035df400>{number = 4, name = (null)}
    2019-12-29 15:32:59.079491+0800 GCDDemo[5180:155847] 取50,还剩150元 - <NSThread: 0x6000035b2e40>{number = 5, name = (null)}
    2019-12-29 15:32:59.079942+0800 GCDDemo[5180:155845] 存100,还剩250元 - <NSThread: 0x6000035df400>{number = 4, name = (null)}
    2019-12-29 15:32:59.080078+0800 GCDDemo[5180:155847] 取50,还剩200元 - <NSThread: 0x6000035b2e40>{number = 5, name = (null)}
    2019-12-29 15:32:59.080475+0800 GCDDemo[5180:155845] 存100,还剩300元 - <NSThread: 0x6000035df400>{number = 4, name = (null)}
    2019-12-29 15:32:59.080968+0800 GCDDemo[5180:155845] 存100,还剩400元 - <NSThread: 0x6000035df400>{number = 4, name = (null)}
    2019-12-29 15:32:59.081394+0800 GCDDemo[5180:155845] 存100,还剩500元 - <NSThread: 0x6000035df400>{number = 4, name = (null)}
    2019-12-29 15:32:59.097714+0800 GCDDemo[5180:155845] 存100,还剩600元 - <NSThread: 0x6000035df400>{number = 4, name = (null)}
    
    

    一开始我们设定银行卡余额为50元,即:self.moneyCount = 50;,假设每次存100元,取50元,操作10次以后,银行卡余额应该剩余300元。但是打印结果却是600元。(哈哈哈,如果现实中真的有这么好的事儿多开心。😁)
    经过分析我们也可以发现,这也是因为多线程造成的问题。如何解决这个问题呢?通过这两个经典的例子,我们引出了线程锁的方案。

    四、线程同步方案(线程锁)

    线程同步最常见的方法就是线程加锁,以下是iOS中常见的线程锁:

    ◼ OSSpinLock(自旋锁)
    ◼ os_unfair_lock
    ◼ pthread_mutex
    ◼ dispatch_semaphore
    ◼ dispatch_queue(DISPATCH_QUEUE_SERIAL)
    ◼ NSLock
    ◼ NSRecursiveLock
    ◼ NSCondition
    ◼ NSConditionLock
    ◼ @synchronized

    我们先来依次看下这几种锁的基本用法:

    (1).◼ OSSpinLock(自旋锁)

    卖票加锁后的代码如下:

    #import "ViewController.h"
    #import <libkern/OSAtomic.h>
    
    @interface ViewController ()
    @property (assign, nonatomic) int ticketsCount;
    @property (assign, nonatomic) OSSpinLock ticketLock;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.ticketLock = OS_SPINLOCK_INIT;
    
        [self ticketTestAction];
    }
    
    /// 卖票演示
    - (void)ticketTestAction {
        self.ticketsCount = 15;
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        dispatch_async(queue, ^{
            for (int i = 0; i < 5; i++) {
                 [self saleSingleTicket];
            }
        });
        dispatch_async(queue, ^{
            for (int i = 0; i < 5; i++) {
                [self saleSingleTicket];
            }
        });
        dispatch_async(queue, ^{
            for (int i = 0; i < 5; i++) {
                [self saleSingleTicket];
            }
        });
    }
    
    /// 卖票事件
    - (void)saleSingleTicket {
        // 加锁
        OSSpinLockLock(&_ticketLock);
        int oldTicketsCount = self.ticketsCount;
        sleep(.2);
        oldTicketsCount--;
        self.ticketsCount = oldTicketsCount;
        NSLog(@"还剩%d张票 - %@", oldTicketsCount, [NSThread currentThread]);
        // 解锁
        OSSpinLockUnlock(&_ticketLock);
    }
    @end
    

    此时,加锁后,我们再次运行查看结果,发现票的剩余张数都是正常的了,说明OSSpinLock这个锁解决了刚刚多线程的问题。

    总结OSSpinLock锁的核心代码如下
    需要导入头文件 #import <libkern/OSAtomic.h>

        // 初始化锁
        OSSpinLock *lock = OS_SPINLOCK_INIT;
        // 尝试加锁(如果需要等待就不加锁,直接返回false;如果不需要等待就加锁,返回true)
        bool result = OSSpinLockTry(&lock);
        // 加锁
        OSSpinLockLock(&lock);
        // 解锁
        OSSpinLockUnlock(&lock);
    
    (2).◼ os_unfair_lock

    需要导入头文件 #import <os/lock.h>

    os_unfair_lock核心代码如下:

        // 初始化
        os_unfair_lock *lock = &OS_UNFAIR_LOCK_INIT;
        // 尝试加锁
        os_unfair_lock_trylock(lock);
        // 加锁
        os_unfair_lock_lock(lock);
        // 解锁
        os_unfair_lock_unlock(lock);
    
    (3).◼ pthread_mutex

    需要导入头文件 #import <pthread.h>

    pthread_mutex -默认锁 核心代码如下:

        // 初始化锁的属性
        pthread_mutexattr_t attr;
        pthread_mutexattr_init(&attr);
        pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
        // 初始化锁
        pthread_mutex_t mutex ;
        pthread_mutex_init(&mutex, &attr);
        // 尝试加锁
        pthread_mutex_trylock(&mutex);
        // 加锁
        pthread_mutex_lock(&mutex);
        // 解锁
        pthread_mutex_unlock(&mutex);
        // 销毁相关资料
        pthread_mutexattr_destroy(&attr);
        pthread_mutex_destroy(&mutex);
    

    注意:这里pthread_mutexattr_settype 里面有好几种类型的锁
    #define PTHREAD_MUTEX_NORMAL 0
    #define PTHREAD_MUTEX_ERRORCHECK 1
    #define PTHREAD_MUTEX_RECURSIVE 2 -- 递归锁
    #define PTHREAD_MUTEX_DEFAULT
    PTHREAD_MUTEX_NORMAL

    pthread_mutex -条件锁 核心代码如下:

      // 初始化锁
         pthread_mutex_t mutex;
        // NULL代表使用默认属性
        pthread_mutex_init(&mutex, NULL);
        // 初始化条件
        pthread_cond_t condition;
        pthread_cond_init(&condition, NULL);
        // 等待条件(进入休眠解锁;唤醒后加锁)
        pthread_cond_wait(&condition, &mutex);
        // 激活一个等待该条件的线程
        pthread_cond_signal(&condition);
        // 激活所有等待该条件的线程
        pthread_cond_broadcast(&condition);
        // 销毁资源
        pthread_mutex_destroy(&mutex);
        pthread_cond_destroy(&condition);
    
    (4).◼NSLock

    NSLock是对mutex普通锁的封装

    NSLock核心代码如下:

        // 初始化
        NSLock *lock = [[NSLock alloc] init];
        // 加锁
        [lock lock];
        // 解锁
        [lock unlock];
    
    
    (5).◼ NSRecursiveLock

    NSRecursiveLock也是对mutex递归锁的封装,API跟NSLock基本一致

    NSRecursiveLock核心代码如下:

        // 初始化
        NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];
        // 加锁
        [lock lock];
        // 解锁
        [lock unlock];
    
    (6).◼NSCondition

    NSCondition是对mutex和cond的封装

    NSCondition核心代码如下:

        // 初始化
        NSCondition *condition = [[NSCondition alloc] init];
        // 加锁
        [condition lock];
        // 解锁
        [condition unlock];
    
    (7).◼ NSConditionLock

    NSConditionLock是对NSCondition的进一步封装,可以设置具体的条件值

    NSConditionLock核心代码如下:

        // 初始化
        NSConditionLock *condition = [[NSConditionLock alloc]initWithCondition:0];
        // 获得锁
        [condition lockWhenCondition:1];
        // 解锁
        [condition unlockWithCondition:1];
    
    (8).◼ dispatch_semaphore

    dispatch_semaphore核心代码如下:

       // 信号量初始值
        int value = 1;
        // 初始化信号量
        dispatch_semaphore_t  semaphore = dispatch_semaphore_create(value);
        // 如果信号量的值<= 0 当前线程进入休眠等待
        // 如果信号量的值> 0  信号量值减1,执行下面的代码
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        // 让信号量的值加1
        dispatch_semaphore_signal(semaphore);
    
    (9).◼dispatch_queue(DISPATCH_QUEUE_SERIAL)

    核心代码如下:

        dispatch_queue_t queue = dispatch_queue_create(@"local_queue", DISPATCH_QUEUE_SERIAL);
        dispatch_sync(queue, ^{
            //执行任务
        });
    
    (10).◼ @synchronized

    synchronized核心代码如下:
    @synchronized是对mutex递归锁的封装
    @synchronized(obj)内部会生成obj对应的递归锁,然后进行加锁、解锁操作

         // obj加锁的对象,可以是self 或者具体的static对象
        @synchronized (obj) {
            //执行任务
        }
    

    以上几种锁的用法简单介绍完了。它们的性能从高到低,排序依次是:

    性能从高到低排序
    ◼ 1. os_unfair_lock
    ◼ 2. OSSpinLock
    ◼ 3. dispatch_semaphore
    ◼ 4. pthread_mutex
    ◼ 5. dispatch_queue(DISPATCH_QUEUE_SERIAL)
    ◼ 6. NSLock
    ◼ 7. NSCondition
    ◼ 8. pthread_mutex(recursive)
    ◼ 9. NSRecursiveLock
    ◼ 10. NSConditionLock
    ◼ 11. @synchronized

    @synchronized加锁方案是最不推荐的一种,因为新能最差。

    iOS中常见的几种锁及其用法就简单介绍到这里,方便自己日后查阅学习。

    相关文章

      网友评论

          本文标题:iOS-常见锁的类型及用法

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