主頁 > 移動端開發 > iOS之深入決議保證執行緒安全的“鎖”的使用和性能分析

iOS之深入決議保證執行緒安全的“鎖”的使用和性能分析

2021-04-12 10:29:16 移動端開發

一、執行緒安全

  • 在平時的開發中經常使用到多執行緒,在使用多執行緒的程序中,難免會遇到資源競爭的問題,那么怎么來避免出現這種問題呢?
  • 當一個執行緒訪問資料的時候,其他的執行緒不能對其進行訪問,直到該執行緒訪問完畢,簡單來講就是在同一時刻,對同一個資料操作的執行緒只有一個,只有確保了這樣,才能使資料不會被其他執行緒影響,而執行緒不安全,則是在同一時刻可以有多個執行緒對該資料進行訪問,從而得不到預期的結果,
  • 比如寫檔案和讀檔案,當一個執行緒在寫檔案的時候,理論上來說,如果這個時候另一個執行緒來直接讀取的話,那么得到的結果可能是無法預料的,
  • 通常使用鎖的機制來保證執行緒安全,即確保同一時刻只有同一個執行緒來對同一個資料源進行訪問,

二、鎖的使用

① 互斥鎖
  • 互斥鎖是一種用于多執行緒編程中,防止兩條執行緒同時對同一公共資源(例如全域變數)進行讀寫的機制,該目的是通過將代碼切成一個個臨界區而達成,
  • 互斥鎖主要是@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

下一篇:Android 自定義相機Camera流程

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more