iOS 的锁

 

 

在 ibireme 的 不再安全的 OSSpinLock 一文中,有一张图片简单的比较了各种锁的加解锁性能:

来源:ibireme

本文会按照从上至下(速度由慢至快)的顺序分析每个锁的实现原理。

 

1.@synchronized中传入的object的内存地址,被用作key,通过hash map对应的一个系统维护的递归锁。

场景一

synchronized是使用的递归mutex来做同步。例如:

@synchronized (obj) {

    NSLog(@"1st sync");

    @synchronized (obj) {

        NSLog(@"2nd sync");

    }

}

场景二

@synchronized(nil)不起任何作用

synchronized中传入的object的内存地址,被用作key,通过hash map对应的一个系统维护的递归锁。如果object 被外部访问变化,则就失去了锁的作用,synchronized使用的时候尽量不要传self,这样锁的粒度有点大,可以对需要管理的对象分别@synchronized(self.dataArray) {XXX}

2.NSConditionLock

下面是苹果官方文档的说法:

A lock that can be associated with specific, user-defined conditions.

可以与特定的用户定义条件相关联的锁。

Using an NSConditionLock object, you can ensure that a thread can acquire a lock only if a certain condition is met. Once it has acquired the lock and executed the critical section of code, the thread can relinquish the lock and set the associated condition to something new. The conditions themselves are arbitrary: you define them as needed for your application.

使用NSConditionLock对象,可以确保线程只有在满足特定条件时才能获得锁。一旦获得锁并执行了代码的关键部分,线程就可以释放锁并将相关条件设置为新的。条件本身是任意的:您可以根据应用程序的需要定义它们。

NSConditionLock同样实现了NSLocking协议,试验过程中发现性能很低。

@protocol NSLocking

- (void)lock;

- (void)unlock;

@end

例子:

#import "NSLockTest.h"
@interface NSLockTest()
@property (nonatomic,strong) NSMutableArray * seats;
@property (nonatomic,strong) NSConditionLock *condition;
@end
@implementation NSLockTest
- (void)forTest
{
    self.tickets = @[1,1];
    
    self.condition = [[NSConditionLock alloc]initWithCondition:0];
    NSThread *windowOne = [[NSThread alloc]initWithTarget:self selector:@selector(haircutOne) object:nil];
    [windowOne start];
    
    NSThread *windowTwo = [[NSThread alloc]initWithTarget:self selector:@selector(haircutTwo) object:nil];
    [windowTwo start];
   
    NSThread *windowTuiPiao = [[NSThread alloc]initWithTarget:self selector:@selector(tuiPiao) object:nil];
    [windowTuiPiao start];

}
//一号窗口
-(void)haircuttOne
{
    while (YES) {
        [self.condition lockWhenCondition:1];
        self.seats[0] = @(0);
        [self.condition unlockWithCondition:0];
    }
}
//二号窗口
-(void)haircutTwo
{
    while (YES) {
        [self.condition lockWhenCondition:2];
        self.seats[1] = @(0);
        [self.condition unlockWithCondition:0];
    }
}
- (void)tuiPiao
{
    while (YES) {
        [self.condition lockWhenCondition:0];
        
        static int first = 0;
        if (first == 0) {
            first =1;
            [self.condition unlockWithCondition:1];
            [self.condition unlockWithCondition:2];
        }
        if ([self.seats[0] integerValue] == 0) {
            self.seats[0] = @(1);
            [self.condition unlockWithCondition:1];
        }
        if ([self.seats[1] integerValue] == 0)
        {
            self.seats[1] = @(1);
            [self.condition unlockWithCondition:2];
        }
    }
    
}
@end

设计了一个例子,有两个剪头发窗口,一个收银窗口,同时控制排队的顾客进入这两个窗口。也就是线程锁在某个条件下,等待某个条件触发。

3.NSRecursiveLock 递归锁

NSRecursiveLock实际上定义的是一个递归锁,这个锁可以被同一线程多次请求,而不会引起死锁。asi大量使用的了递归锁。这主要是用在循环或递归操作中。我们先来看一个示例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

NSLock *lock = [[NSLock alloc] init];

 

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

 

    static void (^RecursiveMethod)(int);

 

    RecursiveMethod = ^(int value) {

 

        [lock lock];

        if (value > 0) {

 

            NSLog(@"value = %d", value);

            sleep(2);

            RecursiveMethod(value - 1);

        }

        [lock unlock];

    };

 

    RecursiveMethod(5);

});

这段代码是一个典型的死锁情况。在我们的线程中,RecursiveMethod是递归调用的。所以每次进入这个block时,都会去加一次锁,而从第二次开始,由于锁已经被使用了且没有解锁,所以它需要等待锁被解除,这样就导致了死锁,线程被阻塞住了。调试器中会输出如下信息:

1

2

value = 5

*** -[NSLock lock]: deadlock ( '(null)')   *** Break on _NSLockError() to debug.

在这种情况下,我们就可以使用NSRecursiveLock。它可以允许同一线程多次加锁,而不会造成死锁。递归锁会跟踪它被lock的次数。每次成功的lock都必须平衡调用unlock操作。只有所有达到这种平衡,锁最后才能被释放,以供其它线程使用

所以,对上面的代码进行一下改造,

1

NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];

4.pthread

pthread 除了创建互斥锁,还可以创建递归锁、读写锁、once等锁。稍后会介绍一下如何使用(九牛一毛而已)。如果想深入学习pthread请查阅相关文档、资料单独学习。

下面是我从 YYKit copy 下来的:

#import <pthread.h>

//YYKit
static inline void pthread_mutex_init_recursive(pthread_mutex_t *mutex, bool recursive) {
#define YYMUTEX_ASSERT_ON_ERROR(x_) do { \
__unused volatile int res = (x_); \
assert(res == 0); \
} while (0)
    assert(mutex != NULL);
    if (!recursive) {
        //普通锁
        YYMUTEX_ASSERT_ON_ERROR(pthread_mutex_init(mutex, NULL));
    } else {
        //递归锁
        pthread_mutexattr_t attr;
        YYMUTEX_ASSERT_ON_ERROR(pthread_mutexattr_init (&attr));
        YYMUTEX_ASSERT_ON_ERROR(pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE));
        YYMUTEX_ASSERT_ON_ERROR(pthread_mutex_init (mutex, &attr));
        YYMUTEX_ASSERT_ON_ERROR(pthread_mutexattr_destroy (&attr));
    }
#undef YYMUTEX_ASSERT_ON_ERROR
}

使用方式

        pthread_mutex_lock(&lock);
        NSLog(@"线程 0:睡眠 1 秒");
        pthread_mutex_unlock(&lock);

针对第三点NSRecursiveLock pthread的实现方式如下,效率更好一些。

实现

    __block pthread_mutex_t lock;
    //第二个参数为true生成递归锁
    pthread_mutex_init_recursive(&lock,true);
    
     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        static void (^RecursiveBlock)(int);
        RecursiveBlock = ^(int value) {
            pthread_mutex_lock(&lock);
            if (value > 0) {
                NSLog(@"加锁层数 %d", value);
                sleep(1);
                RecursiveBlock(--value);
            }
            pthread_mutex_unlock(&lock);
        };
        RecursiveBlock(3);
    });

5.NSCondition

使用NSCondition,实现多线程的同步,即,可实现生产者消费者问题。

基本思路是,首先要创建公用的NSCondition实例。然后:
消费者取得锁,取产品,如果没有,则wait,这时会释放锁,直到有线程唤醒它去消费产品;
生产者制造产品,首先也是要取得锁,然后生产,再发signal,这样可唤醒wait的消费者。

 (IBAction)conditionTest:(id)sender
{
    NSLog(@"begin condition works!");
    products = [[NSMutableArray alloc] init];
    condition = [[NSCondition alloc] init];
     
    [NSThread detachNewThreadSelector:@selector(createProducter) toTarget:self withObject:nil];
    [NSThread detachNewThreadSelector:@selector(createConsumenr) toTarget:self withObject:nil];
}
 
- (void)createConsumenr
{
    [condition lock];
    while ([products count] == 0) {
        NSLog(@"wait for products");
        [condition wait];
    }
    [products removeObjectAtIndex:0];
    NSLog(@"comsume a product");
    [condition unlock];
}
 
- (void)createProducter
{
    [condition lock];
    [products addObject:[[NSObject alloc] init]];
    NSLog(@"produce a product");
    [condition signal];
    [condition unlock];
}

代码如上所示,上面wait加while的作用是防止线程退出。

6.NSLock

NSLock

在Cocoa程序中NSLock中实现了一个简单的互斥锁,实现了NSLocking protocol。
lock,加锁
unlock,解锁
tryLock,尝试加锁,如果失败了,并不会阻塞线程,只是立即返回
NOlockBeforeDate:,在指定的date之前暂时阻塞线程(如果没有获取锁的话),如果到期还没有获取锁,则线程被唤醒,函数立即返回NO
使用tryLock并不能成功加锁,如果获取锁失败就不会执行加锁代码了。

- (void)getIamgeName:(NSMutableArray *)imageNames{
    NSString *imageName;
    [lock lock];
    if (imageNames.count>0) {
        imageName = [imageNames lastObject];
        [imageNames removeObject:imageName];
    }
    [lock unlock];
}


7.dispatch_semaphore

  • dispatch_semaphore_t dispatch_semaphore_create(long value):方法接收一个long类型的参数, 返回一个

    先看下相关的3个方法:

  • dispatch_semaphore_t dispatch_semaphore_create(long value):方法接收一个long类型的参数, 返回一个dispatch_semaphore_t类型的信号量,值为传入的参数
  • long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout):接收一个信号和时间值,若信号的信号量为0,则会阻塞当前线程,直到信号量大于0或者经过输入的时间值;若信号量大于0,则会使信号量减1并返回,程序继续住下执行
  • long dispatch_semaphore_signal(dispatch_semaphore_t dsema):使信号量加1并返回

 

信号量主要用处是:

1.线程数据同步-加锁

2.同步执行一个异步任务

8.OSSpinLock

https://blog.ibireme.com/2016/01/16/spinlock_is_unsafe_in_ios/

自旋锁与互斥锁的区别

从实现原理上来讲,互斥锁属于sleep-waiting类型的锁。例如在一个双核的机器上有两个线程(线程A和线程B),它们分别运行在Core0和 Core1上。假设线程A想要通过pthread_mutex_lock操作去得到一个临界区的锁,而此时这个锁正被线程B所持有,那么线程A就会被阻塞 (blocking),Core0 会在此时进行上下文切换(Context Switch)将线程A置于等待队列中,此时Core0就可以运行其他的任务(例如另一个线程C)而不必进行忙等待。而Spin lock则不然,它属于busy-waiting类型的锁,如果线程A是使用pthread_spin_lock操作去请求锁,那么线程A就会一直在 Core0上进行忙等待并不停的进行锁请求,直到得到这个锁为止。

自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是 否该自旋锁的保持者已经释放了锁。

总结
  自旋锁会忙等: 所谓忙等,即在访问被锁资源时,调用者线程不会休眠,而是不停循环在那里,直到被锁资源释放锁。
  互斥锁会休眠: 所谓休眠,即在访问被锁资源时,调用者线程会休眠,此时cpu可以调度其他线程工作。直到被锁资源释放锁。此时会唤醒休眠线程。

优缺点
  自旋锁的优点在于,因为自旋锁不会引起调用者睡眠,所以不会进行线程调度,cpu时间片轮转等耗时操作。所有如果能在很短的时间内获得锁,自旋锁的效率远高于互斥锁。
  缺点在于,自旋锁一直占用CPU,他在未获得锁的情况下,一直运行--自旋,所以占用着CPU,如果不能在很短的时 间内获得锁,这无疑会使CPU效率降低。自旋锁不能实现递归调用。

pthread_mutex 表示互斥锁。互斥锁可以传入不同参数,实现递归锁pthread_mutex(recursive)。NSLock,NSCondition,NSRecursiveLock,NSConditionLock都是内部封装的pthread_mutex,即都属于互斥锁。@synchronized是NSLock的一种封装,牺牲了效率,简洁了语法。

OSSpinLock 表示自旋锁,从上图可以看到自旋锁的效率最高,但是现在的iOS因为优先级反转的问题,已经不安全,所以推荐使用pthread_mutex或者dispatch_semaphore。

 

 

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值