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/

猜你喜欢

转载自blog.csdn.net/li198847/article/details/84893446