一、執行緒安全
- 在平時的開發中經常使用到多執行緒,在使用多執行緒的程序中,難免會遇到資源競爭的問題,那么怎么來避免出現這種問題呢?
- 當一個執行緒訪問資料的時候,其他的執行緒不能對其進行訪問,直到該執行緒訪問完畢,簡單來講就是在同一時刻,對同一個資料操作的執行緒只有一個,只有確保了這樣,才能使資料不會被其他執行緒影響,而執行緒不安全,則是在同一時刻可以有多個執行緒對該資料進行訪問,從而得不到預期的結果,
- 比如寫檔案和讀檔案,當一個執行緒在寫檔案的時候,理論上來說,如果這個時候另一個執行緒來直接讀取的話,那么得到的結果可能是無法預料的,
- 通常使用鎖的機制來保證執行緒安全,即確保同一時刻只有同一個執行緒來對同一個資料源進行訪問,
二、鎖的使用
① 互斥鎖
- 互斥鎖是一種用于多執行緒編程中,防止兩條執行緒同時對同一公共資源(例如全域變數)進行讀寫的機制,該目的是通過將代碼切成一個個臨界區而達成,
- 互斥鎖主要是@synchronized、NSLock、pthread_mutex,
- @synchronized
-
- @synchronized 是 iOS 中最常見的鎖,用法很簡單:
- (void)synchronized {
NSObject *dwobj = [NSObject new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@synchronized(dwobj) {
NSLog(@"執行緒1開始");
sleep(3);
NSLog(@"執行緒1結束");
}
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
@synchronized(dwobj){
NSLog(@"執行緒2");
}
});
}
-
- 運行程式,結果如下:
執行緒1開始
執行緒1結束
執行緒2
-
- 可以看出,在執行緒 1 內容全部輸出之后,才輸出了執行緒 2 的內容,“執行緒1結束”與“執行緒2”都是在“執行緒1開始”之后輸出的,
-
- @synchronized(dwobj) 指令使用的 dwobj 為該鎖的唯一標識,只有當標識相同時,才為滿足互斥,如果執行緒 2 中的 @synchronized(dwobj) 改為 @synchronized(self) ,那么執行緒 2 就不會被阻塞,
-
- @synchronized 指令實作鎖的優點就是不需要在代碼中顯式的創建鎖物件,便可以實作鎖的機制,但作為一種預防措施;
-
- @synchronized 塊會隱式的添加一個例外處理例程來保護代碼,該處理例程會在例外拋出的時候自動的釋放互斥鎖,所以如果不想讓隱式的例外處理例程帶來額外的開銷,可以考慮使用鎖物件,
-
- @sychronized(dwobj){} 內部 dwobj 被釋放或被設為 nil 不會影響鎖的功能,但如果 dwobj 一開始就是 nil,那就會丟失了鎖的功能,
- NSLock
-
- 先看看 iOS 中的 NSLock.h 檔案,從代碼中可以看出,這里定義了幾個類:NSLock、NSConditionLock、NSRecursiveLock、NSCondition,然后有一個 NSLocking 協議:
@protocol NSLocking
- (void)lock;
- (void)unlock;
@end
-
- 雖然 NSLock、NSConditionLock、NSRecursiveLock、NSCondition 都遵循的了 NSLocking 協議,但是它們并不相同,
-
- NSLock 實作了最基本的互斥鎖,遵循了 NSLocking 協議,通過 lock 和 unlock 來進行鎖定和解鎖,
@interface NSLock : NSObject <NSLocking> {
@private
void *_priv;
}
- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end
-
- NSLock 的 lock 與 unlock 操作必須在同一執行緒,否則結果不確定甚至會引起死鎖,使用如下:
NSLock *dwlock = [NSLock new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[dwlock lock];
NSLog(@"執行緒1加鎖成功");
sleep(2);
[dwlock unlock];
NSLog(@"執行緒1解鎖成功");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
[dwlock lock];
NSLog(@"執行緒2加鎖成功");
[dwlock unlock];
NSLog(@"執行緒2解鎖成功");
});
// 運行程式,結果如下
執行緒1加鎖成功
執行緒1解鎖成功
執行緒2加鎖成功
執行緒2解鎖成功
NSLock *dwlock = [NSLock new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[dwlock lock];
NSLog(@"執行緒1加鎖成功");
sleep(2);
[dwlock unlock];
NSLog(@"執行緒1解鎖成功");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if ([dwlock tryLock]) {
NSLog(@"執行緒3加鎖成功");
[dwlock unlock];
NSLog(@"執行緒3解鎖成功");
} else {
NSLog(@"執行緒3加鎖失敗");
}
});
// 運行程式,結果如下
執行緒1加鎖成功
執行緒3加鎖失敗
執行緒1解鎖成功
NSLock *dwlock = [NSLock new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[dwlock lock];
NSLog(@"執行緒1加鎖成功");
sleep(2);
[dwlock unlock];
NSLog(@"執行緒1解鎖成功");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(3);
if ([dwlock tryLock]) {
NSLog(@"執行緒4加鎖成功");
[dwlock unlock];
NSLog(@"執行緒4解鎖成功");
} else {
NSLog(@"執行緒4加鎖失敗");
}
});
// 運行程式,結果如下
執行緒1加鎖成功
執行緒1解鎖成功
執行緒4加鎖成功
執行緒4解鎖成功
NSLock *dwlock = [NSLock new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[dwlock lock];
NSLog(@"執行緒1加鎖成功");
sleep(2);
[dwlock unlock];
NSLog(@"執行緒1解鎖成功");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if ([dwlock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:10]]) {
NSLog(@"執行緒5加鎖成功");
[dwlock unlock];
NSLog(@"執行緒5解鎖成功");
} else {
NSLog(@"執行緒5加鎖失敗");
}
});
// 運行程式,結果如下
執行緒1加鎖成功
執行緒1解鎖成功
執行緒5加鎖成功
執行緒5解鎖成功
-
- 除 lock 和 unlock 方法外,NSLock 還提供了 tryLock 和 lockBeforeDate:兩個方法,
-
- 可以看到 tryLock 并不會阻塞執行緒,[dwlock tryLock] 能加鎖回傳 YES,不能加鎖回傳 NO,然后都會執行后續代碼,
-
- trylock 和 lock 使用場景:當前執行緒鎖失敗,也可以繼續其它任務,用 trylock 合適;當前執行緒只有鎖成功后,才會做一些有意義的作業,那就 lock,沒必要 trylock,
-
- lockBeforeDate: 方法會在所指定 Date 之前嘗試加鎖,會阻塞執行緒,如果在指定時間之前都不能加鎖,則回傳 NO,指定時間之前能加鎖,則回傳 YES,
-
- 由于是互斥鎖,當一個執行緒進行訪問的時候,該執行緒獲得鎖,其他執行緒進行訪問的時候,將被作業系統掛起,直到該執行緒釋放鎖,其他執行緒才能對其進行訪問,從而卻確保了執行緒安全,但是如果連續鎖定兩次,則會造成死鎖問題,
-
- 如果用 NSLock 的話,dwlock 先鎖上了,但未執行解鎖的時候,就會進入遞回的下一層,而再次請求上鎖,阻塞了該執行緒,執行緒被阻塞了,自然后面的解鎖代碼不會執行,而形成了死鎖,而 NSRecursiveLock 遞回鎖就是為了解決這個問題,
- pthread_mutex
-
- pthread 表示 POSIX thread,定義了一組跨平臺的執行緒相關的 API,POSIX 互斥鎖是一種超級易用的互斥鎖;
-
- pthread_mutex 只需要使用 pthread_mutex_init 初始化一個 pthread_mutex_t;
-
- pthread_mutex_lock 或者 pthread_mutex_trylock 來鎖定 ;
-
- pthread_mutex_unlock 來解鎖;
-
- 當使用完成后,記得呼叫 pthread_mutex_destroy 來銷毀鎖,
-
- pthread_mutex 常用 API:
pthread_mutex_init(pthread_mutex_t *restrict _Nonnull, const pthread_mutexattr_t *restrict _Nullable);
pthread_mutex_lock(pthread_mutex_t * _Nonnull);
pthread_mutex_trylock(pthread_mutex_t * _Nonnull);
pthread_mutex_unlock(pthread_mutex_t * _Nonnull);
pthread_mutex_destroy(pthread_mutex_t * _Nonnull);
-
- pthread_mutex 的使用:
__block pthread_mutex_t dwlock;
pthread_mutex_init(&dwlock, NULL);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
pthread_mutex_lock(&dwlock);
NSLog(@"執行緒1開始");
sleep(3);
NSLog(@"執行緒1結束");
pthread_mutex_unlock(&dwlock);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
pthread_mutex_lock(&dwlock);
NSLog(@"執行緒2");
pthread_mutex_unlock(&dwlock);
});
// 運行程式,結果如下:
執行緒1開始
執行緒1結束
執行緒2
② 自旋鎖
- 在自旋鎖中,執行緒會反復檢查變數是否可用,由于執行緒在這個程序中一致保持執行,所以是一種忙等待, 一旦獲取了自旋鎖,執行緒就會一直保持該鎖,直到顯式釋放自旋鎖,
- 自旋鎖避免了行程背景關系的調度開銷,因此對于執行緒只會阻塞很短時間的場合是有效的,對于 iOS 屬性的修飾符 atomic,它自帶一把自旋鎖,
- 自旋鎖主要有 OSSpinLock 和 atomic,
- OSSpinLock 是一種自旋鎖,和互斥鎖類似,都是為了保證執行緒安全的鎖,
- 但二者的區別是不一樣的,對于互斥鎖,當一個執行緒獲得這個鎖之后,其他想要獲得此鎖的執行緒將會被阻塞,直到該鎖被釋放,但自選鎖不一樣,當一個執行緒獲得鎖之后,其他執行緒將會一直回圈在哪里查看是否該鎖被釋放,
- 自旋鎖比較適用于鎖的持有者保存時間較短的情況下,
- 自旋鎖的加鎖,解鎖,嘗試加鎖:
typedef int32_t OSSpinLock;
// 加鎖
void OSSpinLockLock( volatile OSSpinLock *__lock );
// 嘗試加鎖
bool OSSpinLockTry( volatile OSSpinLock *__lock );
// 解鎖
void OSSpinLockUnlock( volatile OSSpinLock *__lock );
- 自旋鎖的使用如下:
#import <libkern/OSAtomic.h>
__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);
});
//運行程式,結果如下:
執行緒1開始
執行緒1結束
執行緒2
- OSSpinLock 在iOS 10.0中被 <os/lock.h> 中的 os_unfair_lock 取代,
- os_unfair_lock 解決了優先級反轉問題,
// 初始化
os_unfair_lock_t unfairLock = &(OS_UNFAIR_LOCK_INIT);
// 加鎖
os_unfair_lock_lock(unfairLock);
// 嘗試加鎖
BOOL b = os_unfair_lock_trylock(unfairLock);
// 解鎖
os_unfair_lock_unlock(unfairLock);
③ 條件鎖
- 條件鎖就是條件變數,當行程的某些資源要求不滿足時就進入休眠,即鎖住了,當資源被分配到了,條件鎖打開了,行程繼續運行,
- 條件鎖主要是 NSConditionLock 和 NSConditio,
- NSConditionLock 物件所定義的互斥鎖可以在使得在某個條件下進行鎖定和解鎖,它和 NSLock 類似,都遵循 NSLocking 協議,方法都類似,只是多了一個 condition 屬性,以及每個操作都多了一個關于 condition 屬性的方法,例如 tryLock、tryLockWhenCondition:,所以 NSConditionLock 可以稱為條件鎖,
- 只有 condition 引數與初始化時候的 condition 相等,lock 才能正確進行加鎖操作,
- unlockWithCondition: 并不是當 condition 符合條件時才解鎖,而是解鎖之后,修改 condition 的值,
@interface NSConditionLock : NSObject <NSLocking> {
@private
void *_priv;
}
- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;
@property (readonly) NSInteger condition;
- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end
- NSConditionLock 使用如下:
NSConditionLock *dwlock = [[NSConditionLock alloc] initWithCondition:0];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[dwlock lock];
NSLog(@"執行緒1加鎖成功");
sleep(1);
[dwlock unlock];
NSLog(@"執行緒1解鎖成功");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
[dwlock lockWhenCondition:1];
NSLog(@"執行緒2加鎖成功");
[dwlock unlock];
NSLog(@"執行緒2解鎖成功");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(2);
if ([dwlock tryLockWhenCondition:0]) {
NSLog(@"執行緒3加鎖成功");
sleep(2);
[dwlock unlockWithCondition:2];
NSLog(@"執行緒3解鎖成功");
} else {
NSLog(@"執行緒3嘗試加鎖失敗");
}
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if ([dwlock lockWhenCondition:2 beforeDate:[NSDate dateWithTimeIntervalSinceNow:10]]) {
NSLog(@"執行緒4加鎖成功");
[dwlock unlockWithCondition:1];
NSLog(@"執行緒4解鎖成功");
} else {
NSLog(@"執行緒4嘗試加鎖失敗");
}
});
// 運行程式,結果如下
執行緒1加鎖成功
執行緒1解鎖成功
執行緒3加鎖成功
執行緒3解鎖成功
執行緒4加鎖成功
執行緒4解鎖成功
執行緒2加鎖成功
執行緒2解鎖成功
- NSCondition 是一種特殊型別的鎖,通過它可以實作不同執行緒的調度,一個執行緒被某一個條件所阻塞,直到另一個執行緒滿足該條件從而發送信號給該執行緒使得該執行緒可以正確的執行,比如說,你可以開啟一個執行緒下載圖片,一個執行緒處理圖片,這樣的話,需要處理圖片的執行緒由于沒有圖片會阻塞,當下載執行緒下載完成之后,則滿足了需要處理圖片的執行緒的需求,這樣可以給定一個信號,讓處理圖片的執行緒恢復運行,
- NSCondition 的物件實際上作為一個鎖和一個執行緒檢查器,鎖上之后其它執行緒也能上鎖,而之后可以根據條件決定是否繼續運行執行緒,即執行緒是否要進入 waiting 狀態,如果進入 waiting 狀態,當其它執行緒中的該鎖執行 signal 或者 broadcast 方法時,執行緒被喚醒,繼續運行之后的方法,
- NSCondition 可以手動控制執行緒的掛起與喚醒,可以利用這個特性設定依賴,
@interface NSCondition : NSObject <NSLocking> {
@private
void *_priv;
}
- (void)wait; // 掛起執行緒
- (BOOL)waitUntilDate:(NSDate *)limit; // 什么時候掛起執行緒
- (void)signal; // 喚醒一條掛起執行緒
- (void)broadcast; // 喚醒所有掛起執行緒
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end
- NSCondition 使用如下:
NSCondition *dwCondition = [NSCondition new];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[dwCondition lock];
NSLog(@"執行緒1執行緒加鎖");
[dwCondition wait];
NSLog(@"執行緒1執行緒喚醒");
[dwCondition unlock];
NSLog(@"執行緒1執行緒解鎖");
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[dwCondition lock];
NSLog(@"執行緒2執行緒加鎖");
if ([dwCondition waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]]) {
NSLog(@"執行緒2執行緒喚醒");
[dwCondition unlock];
NSLog(@"執行緒2執行緒解鎖");
}
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(2);
[dwCondition signal];
});
// 運行程式,結果如下:
執行緒1執行緒加鎖
執行緒2執行緒加鎖
執行緒1執行緒喚醒
執行緒1執行緒解鎖
// 如果 [dwCondition signal]; 改成 [dwCondition broadcast];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(2);
[dwCondition broadcast];
});
// 運行程式,結果如下:
執行緒1執行緒加鎖
執行緒2執行緒加鎖
執行緒1執行緒喚醒
執行緒1執行緒解鎖
執行緒2執行緒喚醒
執行緒2執行緒解鎖
- NSCondition 在加上鎖之后,呼叫條件物件的 wait 或 waitUntilDate: 方法來阻塞執行緒,直到條件物件發出喚醒信號或者超時之后,再進行之后的操作,
- NSCondition 的 signal 和 broadcast 方法的區別在于,signal 只是一個信號量,只能喚醒一個等待的執行緒,想喚醒多個就得多次呼叫,而 broadcast 可以喚醒所有在等待的執行緒,
④ 遞回鎖
- 遞回鎖就是同一個執行緒可以加鎖 N 次而不會引發死鎖,遞回鎖是特殊的互斥鎖,即是帶有遞回性質的互斥鎖,
- 遞回鎖主要為 pthread_mutex(recursive) 和 NSRecursiveLock,
- NSRecursiveLock 是遞回鎖,顧名思義,可以被一個執行緒多次獲得,而不會引起死鎖,
-
- NSRecursiveLock 記錄了成功獲得鎖的次數,每一次成功的獲得鎖,必須有一個配套的釋放鎖和其對應,這樣才不會引起死鎖,
-
- NSRecursiveLock 會記錄上鎖和解鎖的次數,當二者平衡的時候,才會釋放鎖,其它執行緒才可以上鎖成功,
@interface NSRecursiveLock : NSObject <NSLocking> {
@private
void *_priv;
}
- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end
- NSRecursiveLock 使用如下:
NSRecursiveLock *dwlock = [[NSRecursiveLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static void (^RecursiveBlock)(int);
RecursiveBlock = ^(int value) {
[dwlock lock];
NSLog(@"%d加鎖成功",value);
if (value > 0) {
NSLog(@"value:%d", value);
RecursiveBlock(value - 1);
}
[dwlock unlock];
NSLog(@"%d解鎖成功",value);
};
RecursiveBlock(3);
});
// 運行程式,結果如下
3加鎖成功
value:3
2加鎖成功
value:2
1加鎖成功
value:1
0加鎖成功
0解鎖成功
1解鎖成功
2解鎖成功
3解鎖成功
- pthread_mutex(recursive) 的使用如下:
__block pthread_mutex_t dwlock;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&cjlock, &attr);
pthread_mutexattr_destroy(&attr);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static void (^RecursiveBlock)(int);
RecursiveBlock = ^(int value) {
pthread_mutex_lock(&dwlock);
NSLog(@"%d加鎖成功",value);
if (value > 0) {
NSLog(@"value = %d", value);
sleep(1);
RecursiveBlock(value - 1);
}
NSLog(@"%d解鎖成功",value);
pthread_mutex_unlock(&dwlock);
};
RecursiveBlock(3);
});
// 運行程式,結果如下:
3加鎖成功
value = 3
2加鎖成功
value = 2
1加鎖成功
value = 1
0加鎖成功
0解鎖成功
1解鎖成功
2解鎖成功
3解鎖成功
- pthread_mutex(recursive) 的用法和 NSLock 的 lock unlock 用法一致,而它也有一個 pthread_mutex_trylock 方法,pthread_mutex_trylock 和 tryLock 的區別在于,tryLock 回傳的是 YES 和 NO,pthread_mutex_trylock 加鎖成功回傳的是 0,失敗回傳的是錯誤提示碼,
- pthread_mutex(recursive) 作用和 NSRecursiveLock 遞回鎖類似,如果使用 pthread_mutex_init(&theLock, NULL); 初始化鎖的話,上面的代碼的第二部分會出現死鎖現象,使用遞回鎖就可以避免這種現象,
⑤ 信號量
- 信號量是一種更高級的同步機制,互斥鎖可以說是 semaphore 在僅取值0/1時的特例,信號量可以有更多的取值空間,用來實作更加復雜的同步,而不單單是執行緒間互斥,
- dispatch_semaphore 使用信號量機制實作鎖,等待信號和發送信號,
- dispatch_semaphore 是 GCD 用來同步的一種方式,與它相關的只有三個函式,一個是創建信號量,一個是等待信號,一個是發送信號,
- dispatch_semaphore 的機制就是當有多個執行緒進行訪問的時候,只要有一個獲得了信號,其他執行緒的就必須等待該信號釋放,
- dispatch_semaphore 的常用相關API:
dispatch_semaphore_create(long value);
dispatch_semaphore_wait(dispatch_semaphore_t _Nonnull dsema, dispatch_time_t timeout);
dispatch_semaphore_signal(dispatch_semaphore_t _Nonnull dsema);
- dispatch_semaphore 的使用如下:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 6 * NSEC_PER_SEC);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(semaphore, overTime);
NSLog(@"執行緒1開始");
sleep(5);
NSLog(@"執行緒1結束");
dispatch_semaphore_signal(semaphore);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
dispatch_semaphore_wait(semaphore, overTime);
NSLog(@"執行緒2開始");
dispatch_semaphore_signal(semaphore);
});
// 執行結果如下:
執行緒1開始
執行緒1結束
執行緒2開始
// 如果將上面的 overTime 改成 3 秒,執行結果如下
執行緒1開始
執行緒2開始
執行緒1結束
- dispatch_semaphore 和 NSCondition 類似,都是一種基于信號的同步方式,但 NSCondition 信號只能發送,不能保存(如果沒有執行緒在等待,則發送的信號會失效),而 dispatch_semaphore 能保存發送的信號,dispatch_semaphore 的核心是 dispatch_semaphore_t 型別的信號量,
- dispatch_semaphore_create(1) 方法可以創建一個 dispatch_semaphore_t 型別的信號量,設定信號量的初始值為 1,注意,這里的傳入的引數必須大于或等于 0,否則 dispatch_semaphore_create 會回傳 NULL,
- dispatch_semaphore_wait(semaphore, overTime); 方法會判斷 semaphore 的信號值是否大于 0,大于 0 不會阻塞執行緒,消耗掉一個信號,執行后續任務,如果信號值為 0,該執行緒會和 NSCondition 一樣直接進入 waiting 狀態,等待其他執行緒發送信號喚醒執行緒去執行后續任務,或者當 overTime 時限到了,也會執行后續任務,
- dispatch_semaphore_signal(semaphore); 發送信號,如果沒有等待的執行緒接受信號,則使 signal 信號值加一(做到對信號的保存),
- 一個 dispatch_semaphore_wait(semaphore, overTime); 方法會去對應一個 dispatch_semaphore_signal(semaphore); 看起來像 NSLock 的 lock 和 unlock,其實可以這樣理解,區別只在于有信號量這個引數,lock unlock 只能同一時間,一個執行緒訪問被保護的臨界區,而如果 dispatch_semaphore 的信號量初始值為 x ,則可以有 x 個執行緒同時訪問被保護的臨界區,
⑥ 讀寫鎖
- 讀寫鎖實際是一種特殊的自旋鎖,將對共享資源的訪問分成讀者和寫者,讀者只對共享資源進行讀訪問,寫者則需要對共享資源進行寫操作,這種鎖相對于自旋鎖而言,能提高并發性,
- 一個讀寫鎖同時只能有一個寫者或者多個讀者,但不能既有讀者又有寫者,在讀寫鎖保持期間也是搶占失效的,
- 如果讀寫鎖當前沒有讀者,也沒有寫者,那么寫者可以立刻獲得讀寫鎖,否則它必須自旋在那里, 直到沒有任何寫者或讀者;如果讀寫鎖沒有寫者,那么讀者可以立,
- 其實基本的鎖就包括三類:自旋鎖、互斥鎖、讀寫鎖,其他的比如條件鎖、遞回鎖、信號量都是上層的封裝和實作,
三、鎖的性能
- 借用不再安全的 OSSpinLock中的對鎖的性能測驗,只是測驗了單執行緒的情況,不能反映多執行緒下的實際性能,還有這里比較的只是加鎖立馬解鎖的時間消耗,并沒有計算競爭時候的時間消耗,結果如下:

- 可以看到除了 OSSpinLock 外,dispatch_semaphore 和 pthread_mutex 性能是最高的,有訊息稱,蘋果在新系統中已經優化了 pthread_mutex 的性能,所以它看上去和 OSSpinLock 差距并沒有那么大了,
- 其實每一種鎖基本上都是加鎖、等待、解鎖的步驟,理解了這三個步驟就可以快速學會各種鎖的用法,
- @synchronized 的效率最低,不過它的確用起來最方便,所以如果沒什么性能瓶頸的話,可以選擇使用 @synchronized,
- 當性能要求較高時候,可以使用 pthread_mutex 或者 dispath_semaphore,由于 OSSpinLock 不能很好的保證執行緒安全,而在只有在 iOS10 中才有 os_unfair_lock ,所以,前兩個是比較好的選擇,既可以保證速度,又可以保證執行緒安全,
- 對于 NSLock 及其子類,速度來說 NSLock < NSCondition < NSRecursiveLock < NSConditionLock ,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/275027.html
標籤:其他
上一篇:鴻蒙WebView
