iOS 杂货铺

iOS中的锁

2018-08-09  本文已影响138人  ROBIN2015

iOS中的锁

前言

写在前面:

对比自旋锁和互斥锁异同:

iOS中常用的加锁机制:

代码

介绍

1. @synchronized

先上代码:

- (void)test {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        @synchronized(self){
            NSLog(@"Thread one start ...");
            sleep(3);
            NSLog(@"Thread one end ...");
        }
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        @synchronized(self){
            NSLog(@"Thread two end ...");
        }
    });
}

执行结果:

2018-08-08 14:17:17.927986+0800 TreadTest[88534:2170294] Thread one start ...
2018-08-08 14:17:20.930640+0800 TreadTest[88534:2170294] Thread one end ...
2018-08-08 14:17:20.931061+0800 TreadTest[88534:2170296] Thread two end ...

@synchronized(obj)指令使用obj作为标识, 只有obj是同一个对象时, 餐能满足互斥条件;
特点:

注意:

Objective-C类自身也是对象所以可以对这些类对象使用synchronized,此时是对整个对象类型进行线程同步。

@synchronized ([self class]) {
    }

关于 @synchronized,这儿比你想知道的还要多

2. dispatch_semaphore

代码:

- (void)test {
    dispatch_semaphore_t signal = dispatch_semaphore_create(1);
    dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);
    int timeSleep = 2;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        dispatch_semaphore_wait(signal, timeout);
        NSLog(@"SemaphoreTest Thread one start ...");
        sleep(timeSleep);
        NSLog(@"SemaphoreTest Thread one end ...");
        dispatch_semaphore_signal(signal);
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        dispatch_semaphore_wait(signal, timeout);
        NSLog(@"SemaphoreTest Thread two end ...");
        dispatch_semaphore_signal(signal);
    });
}

timeSleep=2, 输出:

2018-08-08 17:45:49.321698+0800 TreadTest[4121:2420094] SemaphoreTest Thread one start ...
2018-08-08 17:45:51.325003+0800 TreadTest[4121:2420094] SemaphoreTest Thread one end ...
2018-08-08 17:45:51.325509+0800 TreadTest[4121:2420096] SemaphoreTest Thread two end ...

timeSleep=4, 超时输出:

2018-08-08 17:46:45.776442+0800 TreadTest[4159:2421529] SemaphoreTest Thread one start ...
2018-08-08 17:46:48.779212+0800 TreadTest[4159:2421525] SemaphoreTest Thread two end ...
2018-08-08 17:46:49.777714+0800 TreadTest[4159:2421529] SemaphoreTest Thread one end ...

dispatch_semaphore是GCD用来同步的一种方式,相关函数:

3. NSLock

NSLock是Cocoa最基本的锁对象, 主要包含四个方法:

常规用法:

- (void)test {
    NSLock *lock = [NSLock new];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [lock lock];
        NSLog(@"Thread one start ...");
        sleep(3);
        NSLog(@"Thread one end ...");
        [lock unlock];
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [lock lock];
        NSLog(@"Thread two end ...");
        [lock unlock];
    });
}

输出:

2018-08-08 18:03:39.690424+0800 TreadTest[4511:2438866] Thread one start ...
2018-08-08 18:03:42.693269+0800 TreadTest[4511:2438866] Thread one end ...
2018-08-08 18:03:42.693767+0800 TreadTest[4511:2438865] Thread two end ...

尝试获取加锁:

- (void)testTry {
    NSLock *lock = [NSLock new];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [lock lock];
        NSLog(@"Thread one start ...");
        sleep(3);
        NSLog(@"Thread one end ...");
        [lock unlock];
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        if ([lock tryLock]) {
            NSLog(@"Thread two get lock ...");
            [lock unlock];
        } else {
            NSLog(@"Thread two fail to get lock ...");
        }
    });
}

tryLock会尝试加锁, 如果加锁失败直接返回NO, 不会阻塞线程
输出:

2018-08-08 19:18:04.520605+0800 TreadTest[5962:2484928] Thread one start ...
2018-08-08 19:18:04.520609+0800 TreadTest[5962:2484929] Thread two fail to get lock ...
2018-08-08 19:18:07.523243+0800 TreadTest[5962:2484928] Thread one end ...

lockBeforeDate会在超时之前一直尝试获取加锁, 如果超则返回NO
获取超时:

- (void)testLockDate {
    NSLock *lock = [NSLock new];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"Thread one start ...");
        sleep(3);
        NSLog(@"Thread one end ...");
        [lock unlock];
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        if ([lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:5]]) {
            NSLog(@"Thread two get lock ...");
            [lock unlock];
        } else {
            NSLog(@"Thread two fail to get lock ...");
        }
    });
}

输出:

2018-08-08 19:20:27.955699+0800 TreadTest[6043:2488268] Thread one start ...
2018-08-08 19:20:30.956558+0800 TreadTest[6043:2488268] Thread one end ...
2018-08-08 19:20:30.957228+0800 TreadTest[6043:2488272] Thread two get lock ...

4 NSRecursiveLock

- (void)test {
//    NSLock *lock = [[NSLock alloc] init];
    NSRecursiveLock *lock = [[NSRecursiveLock 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(1);
                RecursiveMethod(value - 1);
            }
            [lock unlock];
        };
        
        RecursiveMethod(5);
    });
}

输出:

2018-08-08 19:39:20.174989+0800 TreadTest[6500:2511354] value = 5
2018-08-08 19:39:21.180232+0800 TreadTest[6500:2511354] value = 4
2018-08-08 19:39:22.182961+0800 TreadTest[6500:2511354] value = 3
2018-08-08 19:39:23.186836+0800 TreadTest[6500:2511354] value = 2
2018-08-08 19:39:24.190228+0800 TreadTest[6500:2511354] value = 1

NSRecursiveLock实际上定义的是一个递归锁,这个锁可以被同一线程多次请求,而不会引起死锁。
递归锁会跟踪它被lock的次数。每次成功的lock都必须平衡调用unlock操作。只有所有达到这种平衡,锁最后才能被释放,以供其它线程使用.

如果将NSRecursiveLock改为NSLock, 则会引起死锁:

2018-08-08 19:36:25.251656+0800 TreadTest[6422:2507784] value = 5

另外, NSRecursiveLock提供了另外尝试加锁的方法:

5 NSConditionLock

 - (void)test {
    NSMutableArray *products = [NSMutableArray array];
//    NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:2];
    NSConditionLock *lock = [[NSConditionLock alloc] init];;

    NSInteger HAS_DATA = 1;
    NSInteger NO_DATA = 0;
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        while (1) {
            [lock lockWhenCondition:NO_DATA];
            [products addObject:[[NSObject alloc] init]];
            NSLog(@"produce a product,总量:%zi",products.count);
            [lock unlockWithCondition:HAS_DATA];
            sleep(1);
        }
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        while (1) {
            NSLog(@"wait for product");
            [lock lockWhenCondition:HAS_DATA];
            [products removeObjectAtIndex:0];
            NSLog(@"custome a product");
            [lock unlockWithCondition:NO_DATA];
        }
    });
}

输出:

2018-08-08 19:52:43.013061+0800 TreadTest[6848:2528186] wait for product
2018-08-08 19:52:43.013075+0800 TreadTest[6848:2528187] produce a product,总量:1
2018-08-08 19:52:43.013229+0800 TreadTest[6848:2528186] custome a product
2018-08-08 19:52:43.013396+0800 TreadTest[6848:2528186] wait for product
2018-08-08 19:52:44.014761+0800 TreadTest[6848:2528187] produce a product,总量:1
2018-08-08 19:52:44.014980+0800 TreadTest[6848:2528186] custome a product
2018-08-08 19:52:44.015118+0800 TreadTest[6848:2528186] wait for product
2018-08-08 19:52:45.018647+0800 TreadTest[6848:2528187] produce a product,总量:1
2018-08-08 19:52:45.018906+0800 TreadTest[6848:2528186] custome a product

NSConditionLock有一个很重要的属性:

@property (readonly) NSInteger condition;

执行[lock lockWhenCondition:value]时, 只有value== lock.condition时, 才能获得锁;

执行[lock unlockWithCondition:value]时, 就会把lock.condition设置为value, 以供其他线程按照该条件获取锁.

NSConditionLock可以接受不同的条件, 进而影响不同线程之间的加锁状态.

6 NSCondition

- (void)test {
    NSCondition *condition = [[NSCondition alloc] init];
    NSMutableArray *products = [NSMutableArray array];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        while (1) {
            [condition lock];
            NSLog(@"custome lock");
            if ([products count] == 0) {
                NSLog(@"wait for product");
                [condition wait];
            }
            [products removeObjectAtIndex:0];
            NSLog(@"custome a product");
            [condition unlock];
            NSLog(@"custome unlock");
        }
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        while (1) {
            [condition lock];
            NSLog(@"factory lock");
            [products addObject:[[NSObject alloc] init]];
            NSLog(@"produce a product,总量:%zi",products.count);
            [condition signal];
            NSLog(@"signal a product,总量:%zi",products.count);
            [condition unlock];
            NSLog(@"factory unlock");
            sleep(1);
        }
        
    });
}

输出:

2018-08-08 20:18:56.220689+0800 TreadTest[7570:2561183] custome lock
2018-08-08 20:18:56.220809+0800 TreadTest[7570:2561183] wait for product
2018-08-08 20:18:56.220908+0800 TreadTest[7570:2561187] factory lock
2018-08-08 20:18:56.221005+0800 TreadTest[7570:2561187] produce a product,总量:1
2018-08-08 20:18:56.221779+0800 TreadTest[7570:2561187] signal a product,总量:1
2018-08-08 20:18:56.221865+0800 TreadTest[7570:2561187] factory unlock
2018-08-08 20:18:56.221870+0800 TreadTest[7570:2561183] custome a product
2018-08-08 20:18:56.221985+0800 TreadTest[7570:2561183] custome unlock
2018-08-08 20:18:56.222044+0800 TreadTest[7570:2561183] custome lock
2018-08-08 20:18:56.222296+0800 TreadTest[7570:2561183] wait for product

NSCondition是一种最基本的条件锁。手动控制线程wait和signal。

[condition lock]一般用于多线程同时访问、修改同一个数据源,保证在同一时间内数据源只被访问、修改一次,其他线程的命令需要在lock 外等待,只到unlock ,才可访问;

[condition unlock]与lock 同时使用

[condition wait]让当前线程处于等待状态, 但是condition并未解锁

[condition signal]CPU发信号告诉线程不用在等待,可以继续执行, 但是需要当前线程unlock之后, 其他wait线程才能继续

7 pthread_mutex

递归锁

- (void)test {
    __block pthread_mutex_t theLock;
    pthread_mutex_init(&theLock, NULL);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        pthread_mutex_lock(&theLock);
        NSLog(@"需要线程同步的操作1 开始");
        sleep(3);
        NSLog(@"需要线程同步的操作1 结束");
        pthread_mutex_unlock(&theLock);
        
    });    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1);
        pthread_mutex_lock(&theLock);
        NSLog(@"需要线程同步的操作2");
        pthread_mutex_unlock(&theLock);
        
    });
}

输出:

2018-08-08 20:29:14.289222+0800 TreadTest[7848:2573203] 需要线程同步的操作1 开始
2018-08-08 20:29:17.294626+0800 TreadTest[7848:2573203] 需要线程同步的操作1 结束
2018-08-08 20:29:17.295094+0800 TreadTest[7848:2573206] 需要线程同步的操作2

pthread_mutex需要引入#import <pthread/pthread.h>, 具体方法:

8 pthread_mutex(recursive)

- (void)test {
    __block pthread_mutex_t theLock;
    //pthread_mutex_init(&theLock, NULL);
    
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutex_init(&theLock, &attr);
    pthread_mutexattr_destroy(&attr);
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        static void (^RecursiveMethod)(int);
        
        RecursiveMethod = ^(int value) {
            
            pthread_mutex_lock(&theLock);
            if (value > 0) {
                
                NSLog(@"value = %d", value);
                sleep(1);
                RecursiveMethod(value - 1);
            }
            pthread_mutex_unlock(&theLock);
        };
        
        RecursiveMethod(5);
    });
}

输出:

2018-08-08 21:31:16.271338+0800 TreadTest[12858:2652044] value = 5
2018-08-08 21:31:17.276607+0800 TreadTest[12858:2652044] value = 4
2018-08-08 21:31:18.281560+0800 TreadTest[12858:2652044] value = 3
2018-08-08 21:31:19.283039+0800 TreadTest[12858:2652044] value = 2
2018-08-08 21:31:20.284007+0800 TreadTest[12858:2652044] value = 1

pthread_mutex为了防止在递归的情况下出现死锁而出现的递归锁。

作用和NSRecursiveLock递归锁类似。

如果使用pthread_mutex_init(&theLock, NULL);初始化锁的话,上面的代码会出现死锁现象。

9 OSSpinLock

- (void)test {
    __block OSSpinLock theLock = OS_SPINLOCK_INIT;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        OSSpinLockLock(&theLock);
        NSLog(@"需要线程同步的操作1 开始");
        sleep(3);
        NSLog(@"需要线程同步的操作1 结束");
        OSSpinLockUnlock(&theLock);
        
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        OSSpinLockLock(&theLock);
        sleep(1);
        NSLog(@"需要线程同步的操作2");
        OSSpinLockUnlock(&theLock);
        
    });
    
}

输出:

2018-08-08 21:38:06.443968+0800 TreadTest[13109:2660908] 需要线程同步的操作1 开始
2018-08-08 21:38:09.448119+0800 TreadTest[13109:2660908] 需要线程同步的操作1 结束
2018-08-08 21:38:10.504616+0800 TreadTest[13109:2660909] 需要线程同步的操作2

需要导入#import <libkern/OSAtomic.h>
OSSpinLock 自旋锁,性能最高的锁;原理很简单,就是一直 do while 忙等。它的缺点是当等待时会消耗大量 CPU 资源,所以它不适用于较长时间的任务

自旋锁不会让等待的进入睡眠状态
不再安全的 OSSpinLock中说明了OSSpinLock已经不再安全,在iOS10.0中已经废弃。

10 os_unfair_lock

- (void)test {
    __block os_unfair_lock unfairLock = OS_UNFAIR_LOCK_INIT;
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        os_unfair_lock_lock(&unfairLock);
        NSLog(@"需要线程同步的操作1 开始");
        sleep(3);
        NSLog(@"需要线程同步的操作1 结束");
        os_unfair_lock_unlock(&unfairLock);

    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        if (os_unfair_lock_trylock(&unfairLock)) {
            sleep(1);
            NSLog(@"需要线程同步的操作2");
            os_unfair_lock_unlock(&unfairLock);
        } else {
            NSLog(@"lock fail");
        }

    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        os_unfair_lock_lock(&unfairLock);
        NSLog(@"需要线程同步的操作3 开始");
        sleep(3);
        NSLog(@"需要线程同步的操作3 结束");
        os_unfair_lock_unlock(&unfairLock);
        
    });
}

输出:

2018-08-09 11:36:37.785061+0800 TreadTest[23389:2849366] 需要线程同步的操作1 开始
2018-08-09 11:36:37.785102+0800 TreadTest[23389:2849365] lock fail
2018-08-09 11:36:40.790521+0800 TreadTest[23389:2849366] 需要线程同步的操作1 结束
2018-08-09 11:36:40.791035+0800 TreadTest[23389:2849364] 需要线程同步的操作3 开始
2018-08-09 11:36:43.795681+0800 TreadTest[23389:2849364] 需要线程同步的操作3 结束

输出结果可能不一定, 取决于那个线程先拿到锁

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

自旋锁已经不在安全,然后苹果又整出来个 os_unfair_lock_t ,这个锁解决了优先级反转问题。

底层调用看,为互斥锁,等待os_unfair_lock锁的线程会处于休眠,而并非忙等,等不到锁就休眠(ios10以上使用)

os_unfair_lock_t unfairLock;     
unfairLock = &(OS_UNFAIR_LOCK_INIT);     
os_unfair_lock_lock(unfairLock);     
os_unfair_lock_unlock(unfairLock)

11 pthread_rwlock 读写锁

百度百科:读写锁实际是一种特殊的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。这种锁相对于自旋锁而言,能提高并发性,因为在多处理器系统中,它允许同时有多个读者来访问共享资源,最大可能的读者数为实际的逻辑CPU数。写者是排他性的,一个读写锁同时只能有一个写者或多个读者(与CPU数相关),但不能同时既有读者又有写者

测试读取操作:

- (void)testRead {
    __block pthread_rwlock_t rwlock;
    pthread_rwlock_init(&rwlock,NULL);
    
    int i = 4;
    while (i>0) {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            sleep(1);
            
            NSLog(@"线程0:加锁");
            pthread_rwlock_rdlock(&rwlock);
            NSLog(@"线程0:读");
            pthread_rwlock_unlock(&rwlock);
            NSLog(@"线程0:解锁");
            
        });
        i--;
    }
}

输出:

2018-08-09 14:18:13.626237+0800 TreadTest[32346:2991544] 线程0:加锁
2018-08-09 14:18:13.626241+0800 TreadTest[32346:2991546] 线程0:加锁
2018-08-09 14:18:13.626320+0800 TreadTest[32346:2991545] 线程0:加锁
2018-08-09 14:18:13.626320+0800 TreadTest[32346:2991547] 线程0:加锁
2018-08-09 14:18:13.626779+0800 TreadTest[32346:2991544] 线程0:读
2018-08-09 14:18:13.626792+0800 TreadTest[32346:2991546] 线程0:读
2018-08-09 14:18:13.626973+0800 TreadTest[32346:2991545] 线程0:读
2018-08-09 14:18:13.627002+0800 TreadTest[32346:2991547] 线程0:读
2018-08-09 14:18:13.627102+0800 TreadTest[32346:2991544] 线程0:解锁
2018-08-09 14:18:13.627126+0800 TreadTest[32346:2991546] 线程0:解锁
2018-08-09 14:18:13.627221+0800 TreadTest[32346:2991545] 线程0:解锁
2018-08-09 14:18:13.627241+0800 TreadTest[32346:2991547] 线程0:解锁

不同线程之间并没有阻塞, 即没有等待其他线程解锁就可以进行读取

写操作:

- (void)testWrite {
    __block pthread_rwlock_t rwlock;
    pthread_rwlock_init(&rwlock,NULL);
    int j = 4;
    while (j>0) {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            sleep(1);
            NSLog(@"线程1:加锁");
            pthread_rwlock_wrlock(&rwlock);
            NSLog(@"线程1:写");
            pthread_rwlock_unlock(&rwlock);
            NSLog(@"线程1:解锁");
        });
        j--;
    }
}

输出:

2018-08-09 14:24:48.828117+0800 TreadTest[32581:3000922] 线程1:加锁
2018-08-09 14:24:48.828117+0800 TreadTest[32581:3000937] 线程1:加锁
2018-08-09 14:24:48.828140+0800 TreadTest[32581:3000920] 线程1:加锁
2018-08-09 14:24:48.828178+0800 TreadTest[32581:3000921] 线程1:加锁
2018-08-09 14:24:48.828555+0800 TreadTest[32581:3000922] 线程1:写
2018-08-09 14:24:48.829907+0800 TreadTest[32581:3000922] 线程1:解锁
2018-08-09 14:24:48.829908+0800 TreadTest[32581:3000937] 线程1:写
2018-08-09 14:24:48.830625+0800 TreadTest[32581:3000937] 线程1:解锁
2018-08-09 14:24:48.830638+0800 TreadTest[32581:3000920] 线程1:写
2018-08-09 14:24:48.830981+0800 TreadTest[32581:3000920] 线程1:解锁
2018-08-09 14:24:48.830985+0800 TreadTest[32581:3000921] 线程1:写
2018-08-09 14:24:48.831438+0800 TreadTest[32581:3000921] 线程1:解锁

输出结果显示, 线程的写操作需要等待解锁(sleep时间不同可能造成输出顺序不同, 应该是和线程的时间片有关)

12 NSDistributedLock 分布式锁

分布式锁是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁

Mac下NSDistributedLock同步性能

参考:

iOS开发中的11种锁以及性能对比

iOS中保证线程安全的几种方式与性能对比

OC--各种线程锁

iOS底层原理-多线程

Lock in iOS

iOS 锁的简单实现与总结

iOS-线程安全

互斥锁的实现

上一篇 下一篇

猜你喜欢

热点阅读