锁一般用于在多线程中,保证在一段时期内这段代码只能被某一个线程所访问,从而保证线程同步。在iOS中,常用的锁大致有@synchronized,NSLock,NSCondition,NSConditionLock、NSRecursiveLock和dispatch_semaphore等,如下图所示。
常见锁介绍
锁按照功能分,主要可以分为互斥锁,自旋锁,信号量。
- 互斥锁禁止多个线程同时进入受保护的代码“临界区”(critical section)。
- 自旋锁跟互斥锁一样,但是获取锁操作将自旋在那里,直到该自旋锁的保持者释放了锁。
- 信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况。
@synchronized
@synchronized(obj)指令使用的obj为该锁的唯一标识,同一标识的线程互斥。这是通过一个哈希表来实现的,OC 在底层使用了一个互斥锁的数组(你可以理解为锁池),通过对对象去哈希值来得到对应的互斥锁。@synchronized的内部详细原理可以参考关于 @synchronized,这儿比你想知道的还要多。
NSMutableArray *arr = [NSMutableArray arrayWithObjects:@1,@2, nil];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(1);
@synchronized(arr) {
[arr removeLastObject];
sleep(1);
NSLog(@"%lu in thread1",arr.count);
}
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
@synchronized(arr) {
@synchronized(arr) {
[arr removeLastObject];
NSLog(@"%lu in thread2",arr.count);
sleep(2);
}
}
});
输出结果如下:
2017-12-20 14:55:19.646387+0800 Lock[49276:6948973] 1 in thread2
2017-12-20 14:55:22.653366+0800 Lock[49276:6948972] 0 in thread1
NSConditon
NSConditon是一种条件锁。当其他线程的锁收到signal(单发)或者broadcast(多发)时,如果条件为true,则会上锁。
NSCondition *lock = [[NSCondition alloc] init];
NSMutableArray *array = [[NSMutableArray alloc] init];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[lock lock];
while (!array.count) {
[lock wait];
}
[array removeLastObject];
NSLog(@"remove last object %@",array);
[lock unlock];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[lock lock];
while (!array.count) {
[lock wait];
}
[array removeLastObject];
NSLog(@"remove last object %@",array);
[lock unlock];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(1);
[lock lock];
[array addObjectsFromArray:@[@1,@2]];
NSLog(@"add objects @1,@2");
[lock broadcast];
[lock unlock];
});
}
输出如下:
2017-12-20 15:09:27.322078+0800 Lock[67589:7008671] add objects @1,@2
2017-12-20 15:09:27.322539+0800 Lock[67589:7008668] remove last object (
1
)
2017-12-20 15:09:27.322731+0800 Lock[67589:7008669] remove last object (
)
NSConditionLock
NSConditionLock这种条件锁在初始化时可以指定一个条件,在上锁时,只有满足这个条件才能上锁,在解锁时,可以修改这个条件锁的条件。
NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:1];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[lock lockWhenCondition:0];
NSLog(@"locked this lock in thread 1");
sleep(1);
[lock unlock];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(2);
if ([lock tryLockWhenCondition:1]) {
NSLog(@"locked this lock in thread 2");
sleep(1);
[lock unlockWithCondition:0];
NSLog(@"unlocked this lock in thread 2");
} else {
NSLog(@"fail locked this lock in thread 2");
}
});
输出如下:
2017-12-20 15:16:32.725752+0800 Lock[76697:7037383] locked this lock in thread 2
2017-12-20 15:16:33.726779+0800 Lock[76697:7037383] unlocked this lock in thread 2
2017-12-20 15:16:33.726803+0800 Lock[76697:7037384] locked this lock in thread 1
NSRecursiveLock
递归锁用在需要递归的地方。这种锁不会因为重复上锁而导致死锁。
NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static void (^recursiveBlock)(int);
recursiveBlock = ^(int value) {
[lock lock];
if (value > 0) {
NSLog(@"value is %d",value);
recursiveBlock(value-1);
}
[lock unlock];
};
recursiveBlock(5);
});
输出如下:
2017-12-20 15:21:09.465255+0800 Lock[82850:7057130] value is 5
2017-12-20 15:21:09.465562+0800 Lock[82850:7057130] value is 4
2017-12-20 15:21:09.465739+0800 Lock[82850:7057130] value is 3
2017-12-20 15:21:09.465906+0800 Lock[82850:7057130] value is 2
2017-12-20 15:21:09.466064+0800 Lock[82850:7057130] value is 1
dispatch_semaphore
使用方法非常简单,dispatch_semaphore_create(1)为创建信号,数字表示可以同时几个线程使用信号。为1表示同步使用。如果此处标2就和没设置信号量一样,并发自行运行。如果设置为0,则一律等待overTime时自动释放,所有代码都不执行,理论上也具有同步作用,就是慢点…
dispatch_semaphore_t signal = dispatch_semaphore_create(1);
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 3.0f * NSEC_PER_SEC);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"线程1 等待ing");
dispatch_semaphore_wait(signal, timeout); //signal 值 -1
NSLog(@"线程1 sleep");
sleep(2);
NSLog(@"线程1");
dispatch_semaphore_signal(signal); //signal 值 +1
NSLog(@"线程1 发送信号");
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"线程2 等待ing");
dispatch_semaphore_wait(signal, timeout);
NSLog(@"线程2 sleep");
sleep(2);
NSLog(@"线程2");
dispatch_semaphore_signal(signal);
NSLog(@"线程2 发送信号");
});
输出如下:
2017-12-20 16:28:19.286668+0800 Lock[70914:7323787] 线程2 等待ing
2017-12-20 16:28:19.286670+0800 Lock[70914:7323790] 线程1 等待ing
2017-12-20 16:28:19.286810+0800 Lock[70914:7323787] 线程2 sleep
2017-12-20 16:28:21.290357+0800 Lock[70914:7323787] 线程2
2017-12-20 16:28:21.290556+0800 Lock[70914:7323787] 线程2 发送信号
2017-12-20 16:28:21.290566+0800 Lock[70914:7323790] 线程1 sleep
2017-12-20 16:28:23.294412+0800 Lock[70914:7323790] 线程1
2017-12-20 16:28:23.294572+0800 Lock[70914:7323790] 线程1 发送信号