iOS-多线程02-多线程安全隐患及解决方案(常用锁)

上一篇文章,我们大致了解了,多线程的基本知识和GCD的基本使用,这篇文章,我们主要来看看多线程的隐患和解决方案。
一般在我们项目中,可能会有这种情况:一块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源;或者说是多个线程访问同一个对象、变量、文件等等。这时候,如果不采取一定的措施,很容易引发数据错乱和数据安全的问题。就像我们以前很早就听说过的卖票的例子,如果在卖票过程中不去做相应的限制,这样如果几个窗口同一时间卖票,这样在原来的票数的基础上扣减的票数就会出现问题,就好像下面这张图:


image.png

所以这个时候,我们需要去解决这个问题。类似的例子还有像在银行存钱取钱的操作。如果不做限制,同一时间存钱取钱,也必然会出现问题,如下图:


image.png

这个问题的关键点就是,同一时间,做了不同的操作,每个操作对原来数据都产生了影响,几个影响没有依次进行,导致出现数据错乱。
举个例子,银行账目上原先有1000,在取钱500的同时,又存钱500,这时候大家第一时间就是觉得,还剩余1000,这只是正常现象,如果存取操作同时进行的话,因为基础都是1000,所以,取完钱剩余500,存完钱剩余1500,这两个数据任何一个放回账目上都是不正确的。这就是多条线程同时操作一个对象所引发的问题。
这个,我们解决问题的方案就是:使用线程同步技术(同步,就是协同步调,按预定的先后次序进行)。而在iOS领域中,常见的线程同步技术就是:加锁。

先来看看我们iOS线程同步技术的一些方案,也就是我们接下来要分析的方案:

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

第一种OSSpinLock,如果了解这个锁的同学,应该知道,这个方案已经被苹果废弃,当然,现在仍然可用,只是苹果不推荐使用。不过,我们还是要来看看这个锁的使用:

首先,创建一个base对象BLBaseDemo,里面封装了卖票和存取钱的操作,而且是多线程异步操作,基本代码如下:
/**
 存钱、取钱演示
 */
- (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
{
    int oldMoney = self.money;
    sleep(.2);
    oldMoney += 50;
    self.money = oldMoney;
    
    NSLog(@"存50,还剩%d元 - %@", oldMoney, [NSThread currentThread]);
}

/**
 取钱
 */
- (void)__drawMoney
{
    int oldMoney = self.money;
    sleep(.2);
    oldMoney -= 20;
    self.money = oldMoney;
    
    NSLog(@"取20,还剩%d元 - %@", oldMoney, [NSThread currentThread]);
}

/**
 卖票演示
 */
- (void)ticketTest
{
    self.ticketsCount = 15;
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            [self __saleTicket];
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            [self __saleTicket];
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            [self __saleTicket];
        }
    });
}

/**
 卖1张票
 */
- (void)__saleTicket
{
    int oldTicketsCount = self.ticketsCount;
    sleep(.2);
    oldTicketsCount--;
    self.ticketsCount = oldTicketsCount;
    NSLog(@"还剩%d张票 - %@", oldTicketsCount, [NSThread currentThread]);
}
如果直接调用实例对象的方法,如下:
BLBaseDemo *demo = [[BLBaseDemo alloc] init];
[demo moneyTest];
[demo ticketTest];
在正常流程下,最后的结果是钱剩余400,票剩余0
但是实际打印最后的结果:
2019-09-05 10:56:59.051802+0800 002-多线程加锁方案[60823:35399754] 存50,还剩350元 - <NSThread: 0x6000018dc0c0>{number = 3, name = (null)}
2019-09-05 11:12:48.618159+0800 002-多线程加锁方案[61082:35418439] 还剩2张票 - <NSThread: 0x600000ae4d40>{number = 3, name = (null)}
可以看到,钱和票的结果都是错了,这就是多线程同事操作同一个对象或者方法造成的隐患。

接下来,我们用OSSpinLock加锁方法来解决问题:

首先,我们创建一个子类BLOSSpinLockDemo,继承BLBaseDemo,然后定义两个锁,票锁和钱锁,初始化对象的同时对它们进行初始化。然后重写卖票和存取钱的方法,在方法中加锁解锁:

@interface OSSpinLockDemo()
@property (assign, nonatomic) OSSpinLock moneyLock;
@property (assign, nonatomic) OSSpinLock ticketLock;
@end
- (instancetype)init
{
    if (self = [super init]) {
        self.moneyLock = OS_SPINLOCK_INIT;
        self.ticketLock = OS_SPINLOCK_INIT;
    }
    return self;
}

- (void)__drawMoney
{
    OSSpinLockLock(&_moneyLock);
    
    [super __drawMoney];
    
    OSSpinLockUnlock(&_moneyLock);
}

- (void)__saveMoney
{
    OSSpinLockLock(&_moneyLock);
    
    [super __saveMoney];
    
    OSSpinLockUnlock(&_moneyLock);
}

- (void)__saleTicket
{
    OSSpinLockLock(&_ticketLock);
    
    [super __saleTicket];
    
    OSSpinLockUnlock(&_ticketLock);
}
这样我们调用实例对象的方法
BLBaseDemo *demo = [[BLOSSpinLockDemo alloc] init];
[demo moneyTest];
[demo ticketTest];
所打印结果如下:
2019-09-05 11:17:10.600189+0800 002-多线程加锁方案[61172:35424465] 取20,还剩400元 - <NSThread: 0x600002b37280>{number = 7, name = (null)}
2019-09-05 11:17:10.597382+0800 002-多线程加锁方案[61172:35424478] 还剩0张票 - <NSThread: 0x600002b32680>{number = 6, name = (null)}
这里票和钱的结果都是我们想要的,并且是正确的结果。

总结:OSSpinLock是一个自旋锁(后面再做介绍),通过加锁解锁,可以有效解决多线程隐患问题。
OSSpinLock是第一个同步技术方案,接下来就是os_unfair_lock,在分析这个方案之前,我们先来了解下自旋锁和互斥锁。所谓的自旋锁,即等待锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源,直到锁被打开后,退出忙等,并且进入加锁状态。
而互斥锁,就是等待锁的过程是处理休眠的过程,在这个过程中不会占用CPU资源,一旦锁被打开后,重新唤醒等待的线程,然后进行加锁。
从使用的情况来看,两个锁的主要区别在于,是否会占用CPU资源,当时不是说互斥锁不占用CPU资源,就一定比自旋锁好,因为在唤醒过程中也是会消耗内存的。
只是说OSSpinLock这种自旋锁会出现一种状况,当优先级低的线程加锁之后,优先级高的线程在等待的过程中,可能出现优先级高的线程会一直占着CPU资源,导致优先级低的线程没法释放锁,出现线程死锁状态。而互斥锁虽然在唤醒线程的时候会消耗CPU,但是不会出现死锁状态,相对比较安全,所以目前苹果从iOS10开始就推荐大家用os_unfair_lock来替换OSSpinLock。
接下来,我们来看看os_unfair_lock的使用

@interface BLOSUnfairLockDemo ()

@property (nonatomic, assign) os_unfair_lock moneyLock;
@property (nonatomic, assign) os_unfair_lock ticketLock;

@end

@implementation BLOSUnfairLockDemo

- (instancetype)init
{
    self = [super init];
    if (self) {
        self.moneyLock = OS_UNFAIR_LOCK_INIT;
        self.ticketLock = OS_UNFAIR_LOCK_INIT;
    }
    return self;
}

- (void)__drawMoney {
    
    os_unfair_lock_lock(&_moneyLock);
    
    [super __drawMoney];
    
    os_unfair_lock_unlock(&_moneyLock);
}

- (void)__saveMoney {
    
    os_unfair_lock_lock(&_moneyLock);
    
    [super __saveMoney];
    
    os_unfair_lock_unlock(&_moneyLock);
}

- (void)__saleTicket {
    
    os_unfair_lock_lock(&_ticketLock);
    
    [super __saleTicket];
    
    os_unfair_lock_unlock(&_ticketLock);
}

@end
这样我们调用实例对象的方法
BLBaseDemo *demo = [[BLOSUnfairLockDemo alloc] init];
[demo moneyTest];
[demo ticketTest];
所打印结果如下:
2019-09-05 15:36:21.394991+0800 002-多线程加锁方案[65256:35655513] 取20,还剩400元 - <NSThread: 0x600001de4a00>{number = 7, name = (null)}
2019-09-05 15:36:21.392122+0800 002-多线程加锁方案[65256:35655524] 还剩0张票 - <NSThread: 0x600001dc8f80>{number = 6, name = (null)}
所以os_unfair_lock可以提供线程同步技术,而且目前来看,苹果更希望我们使用它来替换OSSpinLock

接下来,我们来看一个用C语言编写的锁,就是pthread_mutex,mutex也是一种互斥锁。先看看基本的使用

@interface BLMutexDemo ()

@property (nonatomic, assign) pthread_mutex_t ticketMutex;
@property (nonatomic, assign) pthread_mutex_t moneyMutex;

@end

@implementation BLMutexDemo

- (void)initMutex:(pthread_mutex_t *)mutex {
    // 初始化属性
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
    
    // 初始化mutex,参数一是pthread_mutex_t(锁) 参数二是pthread_mutexattr_t(属性)
    pthread_mutex_init(mutex, &attr);
    //使用完属性之后 要销毁
    pthread_mutexattr_destroy(&attr);
}

- (instancetype)init
{
    self = [super init];
    if (self) {
        [self initMutex:&_ticketMutex];
        [self initMutex:&_moneyMutex];
    }
    return self;
}

- (void)__drawMoney {
    
    pthread_mutex_lock(&_moneyMutex);
    
    [super __drawMoney];
    
    pthread_mutex_unlock(&_moneyMutex);
}

- (void)__saveMoney {
    
    pthread_mutex_lock(&_moneyMutex);
    
    [super __saveMoney];
    
    pthread_mutex_unlock(&_moneyMutex);
}

- (void)__saleTicket {
    
    pthread_mutex_lock(&_ticketMutex);
    
    [super __saleTicket];
    
    pthread_mutex_unlock(&_ticketMutex);
}

-(void)dealloc {
    
    pthread_mutex_destroy(&_ticketMutex);
    pthread_mutex_destroy(&_moneyMutex);
}

@end
这样我们调用实例对象的方法
BLBaseDemo *demo = [[BLMutexDemo alloc] init];
[demo moneyTest];
[demo ticketTest];
所打印结果如下:
2019-09-05 16:24:30.859567+0800 002-多线程加锁方案[66031:35707487] 存50,还剩400元 - <NSThread: 0x600001522180>{number = 3, name = (null)}
2019-09-05 16:24:30.857287+0800 002-多线程加锁方案[66031:35707486] 还剩0张票 - <NSThread: 0x600001524ec0>{number = 4, name = (null)}

这是就pthread_mutex_t的基本使用,当然在定义pthread_mutex_t时,我们还可以用这种方式:

// 属性参数传NULL,默认为PTHREAD_MUTEX_DEFAULT模式
pthread_mutex_init(mutex, NULL);

这里,还需要讲一个知识点就是,属性设置,pthread_mutexattr_t有几种模式:

#define PTHREAD_MUTEX_NORMAL        0
#define PTHREAD_MUTEX_ERRORCHECK    1
#define PTHREAD_MUTEX_RECURSIVE     2
#define PTHREAD_MUTEX_DEFAULT       PTHREAD_MUTEX_NORMAL

这里我们再来看看PTHREAD_MUTEX_RECURSIVE这种模式,用这种模式定义的锁,叫做递归锁。顾名思义,这是在递归函数中使用的锁。如下代码:

@interface BLMutexDemo ()

@property (nonatomic, assign) pthread_mutex_t recursiveMutex;

@property (nonatomic, assign) int count;

@end

@implementation BLMutexDemo

- (instancetype)init
{
    self = [super init];
    if (self) {
        self.count = 10;
        
        // 初始化属性
        pthread_mutexattr_t attr;
        pthread_mutexattr_init(&attr);
        pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
        
        // 初始化mutex,参数一是pthread_mutex_t(锁) 参数二是pthread_mutexattr_t(属性)
        pthread_mutex_init(&_recursiveMutex, &attr);
        //使用完属性之后 要销毁
        pthread_mutexattr_destroy(&attr);
        
    }
    return self;
}

- (void)otherTest {
    
    pthread_mutex_lock(&_recursiveMutex);
    
    
    if (self.count > 0) {
        self.count--;
        NSLog(@"count的值 -- %d",self.count);
        [self otherTest];
    }
    pthread_mutex_unlock(&_recursiveMutex);
}

-(void)dealloc {
    
    pthread_mutex_destroy(&_recursiveMutex);
}

@end
这样我们调用实例对象的方法
BLBaseDemo *demo = [[BLMutexDemo alloc] init];
[demo otherTest];
所打印结果如下:
2019-09-05 16:38:41.741313+0800 002-多线程加锁方案[66338:35728207] count的值 -- 9
2019-09-05 16:38:41.741421+0800 002-多线程加锁方案[66338:35728207] count的值 -- 8
2019-09-05 16:38:41.741504+0800 002-多线程加锁方案[66338:35728207] count的值 -- 7
2019-09-05 16:38:41.741587+0800 002-多线程加锁方案[66338:35728207] count的值 -- 6
2019-09-05 16:38:41.741657+0800 002-多线程加锁方案[66338:35728207] count的值 -- 5
2019-09-05 16:38:41.741724+0800 002-多线程加锁方案[66338:35728207] count的值 -- 4
2019-09-05 16:38:41.741809+0800 002-多线程加锁方案[66338:35728207] count的值 -- 3
2019-09-05 16:38:41.741891+0800 002-多线程加锁方案[66338:35728207] count的值 -- 2
2019-09-05 16:38:41.741961+0800 002-多线程加锁方案[66338:35728207] count的值 -- 1
2019-09-05 16:38:41.742033+0800 002-多线程加锁方案[66338:35728207] count的值 -- 0

这就是pthread_mutex_t在PTHREAD_MUTEX_RECURSIVE模式下的使用,不过使用递归模式,要注意需要再同一个线程下使用,才能达到递归加锁解锁的效果。
除了PTHREAD_MUTEX_RECURSIVE递归模式,这里我们还需要知道另外一个知识点,那就是条件(pthread_cond_t)。到这里大家可能觉得知识点有点多,因为毕竟还有好几个锁还没有讲到,其实后面的那些锁,很多都是基于pthread_mutex_t这个锁的基础上来封装的,了解了pthread_mutex_t,也就意味着知道后面的锁的原理。那我们来看看什么是pthread_cond_t,很简单,条件条件,就是到达一种条件的情况下进行操作,来看看代码

@interface BLMutexDemo1 ()

@property (assign, nonatomic) pthread_mutex_t mutex;
@property (assign, nonatomic) pthread_cond_t cond;
@property (strong, nonatomic) NSMutableArray *data;

@end

@implementation BLMutexDemo1

- (instancetype)init
{
    if (self = [super init]) {
        // 初始化属性
        pthread_mutexattr_t attr;
        pthread_mutexattr_init(&attr);
        pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
        // 初始化锁
        pthread_mutex_init(&_mutex, &attr);
        // 销毁属性
        pthread_mutexattr_destroy(&attr);
        
        // 初始化条件
        pthread_cond_init(&_cond, NULL);
        
        self.data = [NSMutableArray array];
    }
    return self;
}

- (void)otherTest
{
    [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
}

// 生产者-消费者模式

// 线程1
// 删除数组中的元素
- (void)__remove
{
    pthread_mutex_lock(&_mutex);
    NSLog(@"__remove - begin");
    
    if (self.data.count == 0) {
        // 等待
        pthread_cond_wait(&_cond, &_mutex);
    }
    
    [self.data removeLastObject];
    NSLog(@"删除了元素");
    
    pthread_mutex_unlock(&_mutex);
}

// 线程2
// 往数组中添加元素
- (void)__add
{
    pthread_mutex_lock(&_mutex);
    
    sleep(1);
    
    [self.data addObject:@"Test"];
    NSLog(@"添加了元素");
    
    // 信号
    pthread_cond_signal(&_cond);
    // 广播
    //    pthread_cond_broadcast(&_cond);
    
    pthread_mutex_unlock(&_mutex);
}

- (void)dealloc
{
    pthread_mutex_destroy(&_mutex);
    pthread_cond_destroy(&_cond);
}

@end
代码大概的主要功能就是,一个数组的添加和删除元素,只是在删除元素的时候,我们需要保证数组先有元素,如果没有元素的话,先去添加元素,这时就可以用pthread_cond_t,先在判断数组元素个数为0的时候,使用pthread_cond_wait(),暂时放开锁,等待通知,然后再添加元素之后,使用pthread_cond_singal()来通知pthread_cond_wait()函数中的等待,这时候pthread_cond_wait()会重新加锁,继续原先删除元素的操作,得到结果。
打印如下:
2019-09-05 16:56:09.054302+0800 002-多线程加锁方案[66701:35751970] __remove - begin
2019-09-05 16:56:10.059322+0800 002-多线程加锁方案[66701:35751971] 添加了元素
2019-09-05 16:56:10.059662+0800 002-多线程加锁方案[66701:35751970] 删除了元素

接来下就是GCD中的信号量dispatch_semaphore,这也是同步技术方案中,比较方便的一直方案。这里就简单举个例子

@interface BLSemaphoreDemo ()

@property (nonatomic, strong) dispatch_semaphore_t semaphore;

@end

@implementation BLSemaphoreDemo

- (instancetype)init
{
    self = [super init];
    if (self) {
        self.semaphore = dispatch_semaphore_create(3);
    }
    return self;
}

-(void)otherTest {
    
    for (int i = 0; i <= 9; i ++) {
        [[[NSThread alloc]initWithTarget:self selector:@selector(test) object:nil] start];
    }
}

- (void)test {
    
    dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
    
    sleep(3);
    
    NSLog(@"test --- %@", [NSThread currentThread]);
    
    dispatch_semaphore_signal(self.semaphore);
}

@end
这样我们调用实例对象的方法
BLBaseDemo *demo = [[BLSemaphoreDemo alloc] init];
[demo otherTest];
所打印结果如下:
2019-09-05 17:07:58.970138+0800 002-多线程加锁方案[67225:35770816] test --- <NSThread: 0x60000007b600>{number = 3, name = (null)}
2019-09-05 17:07:58.970183+0800 002-多线程加锁方案[67225:35770825] test --- <NSThread: 0x60000007bc40>{number = 12, name = (null)}
2019-09-05 17:07:58.970157+0800 002-多线程加锁方案[67225:35770824] test --- <NSThread: 0x60000007bac0>{number = 11, name = (null)}
2019-09-05 17:08:01.974692+0800 002-多线程加锁方案[67225:35770821] test --- <NSThread: 0x60000007bf00>{number = 8, name = (null)}
2019-09-05 17:08:01.974707+0800 002-多线程加锁方案[67225:35770819] test --- <NSThread: 0x60000007b6c0>{number = 6, name = (null)}
2019-09-05 17:08:01.974693+0800 002-多线程加锁方案[67225:35770820] test --- <NSThread: 0x60000007b540>{number = 7, name = (null)}
2019-09-05 17:08:04.978897+0800 002-多线程加锁方案[67225:35770823] test --- <NSThread: 0x60000007b940>{number = 10, name = (null)}
2019-09-05 17:08:04.978897+0800 002-多线程加锁方案[67225:35770822] test --- <NSThread: 0x60000007b640>{number = 9, name = (null)}
2019-09-05 17:08:04.978897+0800 002-多线程加锁方案[67225:35770817] test --- <NSThread: 0x60000007bcc0>{number = 4, name = (null)}
2019-09-05 17:08:07.980190+0800 002-多线程加锁方案[67225:35770818] test --- <NSThread: 0x60000007be40>{number = 5, name = (null)}

总结:在打印的结果中,我们可以看到,每三秒,打印一次,总共9条线程,这就是信号量的作用。他可以控制同时并发的最大线程数量,每一条线程到达dispatch_semaphore_wait()这里时,会去判断semaphore剩余可以使用的线程数,因为我们一开始初始化的时候定义是3条:self.semaphore = dispatch_semaphore_create(3),所以这里最大并发数就是3,这就是为什么每次打印是三条的原因,dispatch_semaphore_signal()这个函数是在当前线程走完时,恢复一条线程的使用,因为我们test函数中,用了sleep(3),所以每条线程都会卡3秒,这只是为了演示效果。
接下来就是NSLock、NSCondition和NSConditionLock的使用,NSLock其实就是针对pthread_mutex_t在默认模式下的封装使用,大致就是:

- (void)__saleTicket
{
    [self.ticketLock lock];
    
    [super __saleTicket];
    
    [self.ticketLock unlock];
}

而NSCondition,其实就是对pthread_cond_t加pthread_mutex_t的封装使用,如下代码:

@interface BLNSConditionDemo ()

@property (nonatomic, strong) NSCondition *con;
@property (strong, nonatomic) NSMutableArray *data;

@end

@implementation BLNSConditionDemo

- (instancetype)init
{
    if (self = [super init]) {
        self.con = [[NSCondition alloc] init];
        self.data = [NSMutableArray array];
    }
    return self;
}


- (void)otherTest
{
    [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
}

- (void)__remove
{
    
    [self.con lock];
    NSLog(@"__remove - begin");
    
    if (self.data.count == 0) {
        [self.con wait];
    }
    
    [self.data removeLastObject];
    NSLog(@"删除了元素");
    
    [self.con unlock];
}

- (void)__add
{
    
    [self.con lock];
    
    sleep(1);
    
    [self.data addObject:@"test"];
    NSLog(@"添加了元素");
    
    [self.con unlock];
    
    [self.con signal];
}

@end

而NSConditionLock,则是对NSCondition的进一步封装,如下

@interface BLNSConditionLockDemo ()

@property (nonatomic, strong) NSConditionLock *con;
@property (strong, nonatomic) NSMutableArray *data;

@end

@implementation BLNSConditionLockDemo

- (instancetype)init
{
    if (self = [super init]) {
        self.con = [[NSConditionLock alloc] initWithCondition:1];
        self.data = [NSMutableArray array];
    }
    return self;
}


- (void)otherTest
{
    [[[NSThread alloc] initWithTarget:self selector:@selector(__one) object:nil] start];
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(__two) object:nil] start];
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(__three) object:nil] start];
}

- (void)__one
{
    [self.con lockWhenCondition:1];
    
    NSLog(@"__one");
    sleep(1);
    
    [self.con unlockWithCondition:2];
}

- (void)__two
{
    [self.con lockWhenCondition:2];
    
    NSLog(@"__two");
    sleep(1);
    
    [self.con unlockWithCondition:3];
}

- (void)__three
{
    [self.con lockWhenCondition:3];
    
    NSLog(@"__three");
    
    [self.con unlock];
}

@end

NSConditionLock主要作用就在于,它可以在满足一个条件的情况下,进行加锁和解锁。
最后还有两种同步加锁的方案,一种是dispatch_queue(DISPATCH_QUEUE_SERIAL),还有一种是@synchronized。
对于dispatch_queue(DISPATCH_QUEUE_SERIAL)大家都知道,因为串行队列,不管你是同步还是异步,它都是依次执行任务的,所以可以达到加锁效果。
而对于@synchronized,可能大家在日常的项目中已经有所用到,大概就是@synchronized(self),其实,在()中,只要是同一个对象,就可以达到加锁的效果,这里我就不做过多说明了,但是这个加锁方式性能上不是很好,因为这简单一句话,封装了很多内容。这里,既然讲到性能,我们也可以进行一定的排序,如下图:


image.png

其实也蛮容易理解的,首先是苹果在iOS中主推的os_unfair_lock,其次就是被它替代的OSSpinLock,然后是GCD的信号量,接着是C语言的pthread_mutex,然后是串行队列,后面NS打头的Foundation框架的对象,都是基于pthread_mutex_t进行封装的,自然往后排。最后就是@synchronized。
这里,推荐大家平常使用的,有信号量和pthread_mutex,因为os_unfair_lock是iOS10之后才出来的,而OSSpinLock又不推荐使用,所以性能又高,又比较兼容的就是信号量和和pthread_mutex。
iOS的多线程隐患和解决方案的几大锁,就先分享到这里,后续可能会有一些补充,希望对大家有所帮助!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,688评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,559评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,749评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,581评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,741评论 3 271
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,684评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,122评论 2 292
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,847评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,441评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,939评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,333评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,783评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,275评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,830评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,444评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,553评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,618评论 2 249

推荐阅读更多精彩内容

  • 锁是一种同步机制,用于多线程环境中对资源访问的限制iOS中常见锁的性能对比图(摘自:ibireme): iOS锁的...
    LiLS阅读 1,445评论 0 6
  • 一. 多线程 1.常见多线程方案 pthread : 纯粹 C 语言的API,跨平台, 线程生命周期程序员管理...
    天明天阅读 4,047评论 0 14
  • 一、简介:多线程在之前进行过一篇详细的基础博客 iOS多线程 二、多线程的基础知识回顾 1.1、iOS中的常见多线...
    IIronMan阅读 828评论 0 4
  • 前言 一块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源,比如多个线程访问同一个对象、同一个变量、同...
    WQ_UESTC阅读 826评论 0 5
  • 一、前言 前段时间看了几个开源项目,发现他们保持线程同步的方式各不相同,有@synchronized、NSLock...
    稻春阅读 453评论 0 0