iOS-常见锁的类型及用法

说起“锁”,一般都要涉及多线程,因为一块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源。比如:多个线程访问同一个对象、同一个变量、同一个文件。当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题。这篇文章中,我将会整理一下 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中常见的几种锁及其用法就简单介绍到这里,方便自己日后查阅学习。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值