NSLock是iOS開發(fā)中最基礎(chǔ)的鎖。它繼承自NSObject,遵守NSLocking協(xié)議。用于處理線程安全問題。
下面我們來看一個例子:
for (int i = 0; i < 200000; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
self.mArray = [NSMutableArray array];
});
}
運行該程序會崩潰,這是因為,我們在不斷地創(chuàng)建array,mArray在不斷的賦新值,釋放舊值,這個時候多線程操作就會可能存在值已經(jīng)被釋放了,而其他線程還在操作,此時就會發(fā)生崩潰。此時就需要我們對程序加鎖。將上述程序改成如下:
NSLock *lock = [[NSLock alloc] init];
for (int i = 0; i < 200000; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[lock lock];
self.mArray = [NSMutableArray array];
[lock unlock];
});
}
程序就能正常運行了,這是因為此時,每一條線程執(zhí)行self.mArray = [NSMutableArray array]的前后,都會有獲取鎖釋放鎖的過程,此時這句代碼是在線程安全的情況下執(zhí)行的,所以并沒有異常問題。
那么NSLock到底做了什么?附上一份Objective-C的源碼:
@interface NSLock : NSObject <NSLocking> {
@private
void *_priv;
}
- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;
@property (nullable, copy) NSString *name;
@end
以上方法只能看到定義,并不能看到實現(xiàn),那我們再通過swift的開源代碼來看看:
open class NSLock: NSObject, NSLocking {
internal var mutex = _MutexPointer.allocate(capacity: 1)
private var timeoutCond = _ConditionVariablePointer.allocate(capacity: 1)
private var timeoutMutex = _MutexPointer.allocate(capacity: 1)
public override init() {
pthread_mutex_init(mutex, nil)
pthread_cond_init(timeoutCond, nil)
pthread_mutex_init(timeoutMutex, nil)
}
deinit {
pthread_mutex_destroy(mutex)
mutex.deinitialize(count: 1)
mutex.deallocate()
deallocateTimedLockData(cond: timeoutCond, mutex: timeoutMutex)
}
// 獲取鎖
open func lock() {
pthread_mutex_lock(mutex)
}
// 釋放鎖
open func unlock() {
pthread_mutex_unlock(mutex)
// Wakeup any threads waiting in lock(before:)
pthread_mutex_lock(timeoutMutex)
pthread_cond_broadcast(timeoutCond)
pthread_mutex_unlock(timeoutMutex)
}
// 嘗試加鎖,不會阻塞線程。true則加鎖成功。
// false則失敗,說明其他線程在加鎖中這個方法無論如何都會立即返回。
open func `try`() -> Bool {
return pthread_mutex_trylock(mutex) == 0
}
// 嘗試在指定NSDate之前加鎖,會阻塞線程。true則加鎖成功。
// false則失敗,說明其他線程在加鎖中這個方法無論如何都會立即返回。
open func lock(before limit: Date) -> Bool {
if pthread_mutex_trylock(mutex) == 0 {
return true
}
return timedLock(mutex: mutex, endTime: limit, using: timeoutCond, with: timeoutMutex)
}
open var name: String?
}
從源碼上看NSLock其實只是對pthread_mutex做了一層簡單的封裝。它屬于互斥鎖的一種。當一個線程進行訪問的時候,該線程獲得鎖,其他線程進行訪問的時候,將被操作系統(tǒng)掛起,直到該線程釋放鎖,其他線程才能對其進行訪問,從而卻確保了線程安全。
- (void)nslockTest {
NSLock *lock = [[NSLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"進入線程1");
sleep(2);
[lock lock];
NSLog(@"執(zhí)行任務(wù)1");
[lock unlock];
NSLog(@"退出線程1");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"進入線程2");
[lock lock];
sleep(5);
NSLog(@"執(zhí)行任務(wù)2");
[lock unlock];
NSLog(@"退出線程2");
});
}
運行程序,控制臺輸出:
通過結(jié)果,我們可以看到雖然程序先進入線程1,但是由于我們在執(zhí)行lock加入了延遲,由于是并發(fā)操作,所以緊接著,會進入線程2,線程2可以立即執(zhí)行lock操作,雖然我們緊接著sleep了5秒鐘,但是由于鎖已經(jīng)被線程2占用,并不會去執(zhí)行線程1的操作,此時線程1就被阻塞了,只有等到線程2執(zhí)行完成解鎖之后才會進入線程1執(zhí)行任務(wù)。這也就完美的體現(xiàn)了互斥鎖的特性。
需要注意的是,NSLock在使用不當?shù)臅r候會造成堵塞線程。