鎖一般用于在多線程中,保證在一段時期內(nèi)這段代碼只能被某一個線程所訪問,從而保證線程同步。在iOS中,常用的鎖大致有@synchronized,NSLock,NSCondition,NSConditionLock、NSRecursiveLock和dispatch_semaphore等,如下圖所示。
image.png
1. 常見鎖介紹
鎖按照功能分,主要可以分為互斥鎖,自旋鎖,信號量。
- 互斥鎖禁止多個線程同時進入受保護的代碼“臨界區(qū)”(critical section)。
- 自旋鎖跟互斥鎖一樣,但是獲取鎖操作將自旋在那里,直到該自旋鎖的保持者釋放了鎖。
- 信號量是一個計數(shù)器,它用來記錄對某個資源(如共享內(nèi)存)的存取狀況。
1.1 @synchronized
@synchronized(obj)指令使用的obj為該鎖的唯一標識,同一標識的線程互斥。這是通過一個哈希表來實現(xiàn)的,OC 在底層使用了一個互斥鎖的數(shù)組(你可以理解為鎖池),通過對對象去哈希值來得到對應的互斥鎖。@synchronized的內(nèi)部詳細原理可以參考關(guān)于 @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);
}
}
});
輸出結(jié)果如下:
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
1.2 NSConditon
NSConditon是一種條件鎖。當其他線程的鎖收到signal(單發(fā))或者broadcast(多發(fā))時,如果條件為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 (
)
1.3 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
1.4 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
1.5 dispatch_semaphore
使用方法非常簡單,dispatch_semaphore_create(1)為創(chuàng)建信號,數(shù)字表示可以同時幾個線程使用信號。為1表示同步使用。如果此處標2就和沒設(shè)置信號量一樣,并發(fā)自行運行。如果設(shè)置為0,則一律等待overTime時自動釋放,所有代碼都不執(zhí)行,理論上也具有同步作用,就是慢點…
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 發(fā)送信號");
});
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 發(fā)送信號");
});
輸出如下:
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 發(fā)送信號
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 發(fā)送信號