尋夢新聞LINE@每日推播熱門推薦文章,趣聞不漏接❤️
作者丨極客學偉
https://juejin.im/post/5b4c179ee51d4518e3117c9f
IOS中常見的幾種鎖介紹示例
常用的各類鎖性能比較
文中Demo 均做到在 XWInterviewDemos
https://github.com/qxuewei/XWInterviewMap/tree/master/_XWCollection/CodeDemos/XWInterviewDemos
1. iOS中的互斥鎖
在編程中,引入對象互斥鎖的概念,來保證共享數據操作的完整性。每個對象都對應於一個可稱為「互斥鎖」的標記,這個標記用來保證在任一時刻,只能有一個線程訪問對象。
1.1 @synchronized (self)
– ( void)lock1 {
@synchronized( self) {
// 加鎖操作
}
}
1.2 NSLock
– ( void)lock2 {
NSLock*xwlock = [[ NSLockalloc] init];
XWLogBlock logBlock = ^ ( NSArray*array) {
[xwlock lock];
for( idobj inarray) {
NSLog( @”%@”,obj);
}
[xwlock unlock];
};
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSArray*array = @[@ 1,@ 2,@ 3];
logBlock(array);
});
}
1.3 pthread
pthread除了創建互斥鎖,還可以創建遞歸鎖、讀寫鎖、once等鎖
__block pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog( @”+++++ 線程1 start”);
pthread_mutex_lock(&mutex);
sleep( 2);
pthread_mutex_unlock(&mutex);
NSLog( @”+++++ 線程1 end”);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog( @”—– 線程2 start”);
pthread_mutex_lock(&mutex);
sleep( 3);
pthread_mutex_unlock(&mutex);
NSLog( @”—– 線程2 end”);
});
}
2. iOS中的遞歸鎖
同一個線程可以多次加鎖,不會造成死鎖
死鎖->
– ( void)lock5 {
NSLock *commonLock = [[NSLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
staticvoid(^XWRecursiveBlock)(int);
XWRecursiveBlock = ^( intvalue) {
[ commonLock lock];
if( value> 0) {
NSLog( @”加鎖層數: %d”, value);
sleep( 1);
XWRecursiveBlock(– value);
}
NSLog( @”程序退出!”);
[ commonLock unlock];
};
XWRecursiveBlock( 3);
});
}
<-死鎖
2.1 NSRecursiveLock
– ( void)lock4 {
NSRecursiveLock *recursiveLock = [[NSRecursiveLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
staticvoid(^XWRecursiveBlock)(int);
XWRecursiveBlock = ^( intvalue) {
[ recursiveLock lock];
if( value> 0) {
NSLog( @”加鎖層數: %d”, value);
sleep( 1);
XWRecursiveBlock(– value);
}
NSLog( @”程序退出!”);
[ recursiveLock unlock];
};
XWRecursiveBlock( 3);
});
}
2.2 pthread
– ( void)lock6 {
__block pthread_mutex_t recursiveMutex;
pthread_mutexattr_t recursiveMutexattr;
pthread_mutexattr_init(&recursiveMutexattr);
pthread_mutexattr_settype(&recursiveMutexattr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&recursiveMutex, &recursiveMutexattr);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
staticvoid(^XWRecursiveBlock)(int);
XWRecursiveBlock = ^( intvalue) {
pthread_mutex_lock(&recursiveMutex);
if( value> 0) {
NSLog( @”加鎖層數: %d”, value);
sleep( 1);
XWRecursiveBlock(– value);
}
NSLog( @”程序退出!”);
pthread_mutex_unlock(&recursiveMutex);
};
XWRecursiveBlock( 3);
});
}
3. 信號量
信號量(Semaphore),有時被稱為信號燈,是在多線程環境下使用的一種設施,是可以用來保證兩個或多個關鍵代碼段不被並發調用。在進入一個關鍵代碼段之前,線程必須獲取一個信號量;一旦該關鍵代碼段完成了,那麼該線程必須釋放信號量。其它想進入該關鍵代碼段的線程必須等待直到第一個線程釋放信號量
3.1 dispatch_semaphore_t
做到 GCD 下同步
– ( void)lock7 {
// dispatch_semaphore_create //創建一個信號量 semaphore
// dispatch_semaphore_signal //發送一個信號 信號量+1
// dispatch_semaphore_wait // 等待信號 信號量-1
/// 需求: 異步線程的兩個操作同步執行
dispatch_semaphore_t semaphone = dispatch_semaphore_create( 0);
NSLog( @”start”);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog( @”async …. “);
sleep( 5);
/// 線程資源 + 1
dispatch_semaphore_signal(semaphone); //信號量+1
});
/// 當前線程資源數量為 0 ,等待激活
dispatch_semaphore_wait(semaphone, DISPATCH_TIME_FOREVER);
NSLog( @”end”);
}
3.2 pthread
– ( void)lock8 {
__block pthread_mutex_t semaphore = PTHREAD_MUTEX_INITIALIZER;
__block pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
NSLog( @”start”);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
pthread_mutex_lock(&semaphore);
NSLog( @”async…”);
sleep( 5);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&semaphore);
});
pthread_cond_wait(&cond, &semaphore);
NSLog( @”end”);
}
4. 條件鎖
4.1 NSCondition
NSCondition 的對象實際上是作為一個鎖和線程檢查器,鎖主要是為了檢測條件時保護數據源,執行條件引發的任務。線程檢查器主要是根據條件決定是否繼續運行線程,即線程是否被阻塞。
NSCondition同樣做到了NSLocking協議,所以它和NSLock一樣,也有NSLocking協議的lock和unlock方法,可以當做NSLock來使用解決線程同步問題,用法完全一樣。
– ( NSMutableArray*)removeLastImage:( NSMutableArray*)images {
if(images.count > 0) {
NSCondition*condition = [[ NSConditionalloc] init];
[condition lock];
[images removeLastObject];
[condition unlock];
NSLog( @”removeLastImage %@”,images);
returnimages;
} else{
returnNULL;
}
}
同時,NSCondition提供更高級的用法。wait和signal,和條件信號量類似。
NSCondition和NSLock、@synchronized等是不同的是,NSCondition可以給每個線程分別加鎖,加鎖後不影響其他線程進入臨界區。這是非常強大。
– ( void)lock10 {
self.conditionArray = [ NSMutableArrayarray];
self.xwCondition = [[ NSConditionalloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[ self.xwCondition lock];
if( self.conditionArray.count == 0) {
NSLog( @”等待製作數組”);
[ self.xwCondition wait];
}
idobj = self.conditionArray[ 0];
NSLog( @”獲取對象進行操作:%@”,obj);
[ self.xwCondition unlock];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[ self.xwCondition lock];
idobj = @”極客學偉”;
[ self.conditionArray addObject:obj];
NSLog( @”創建了一個對象:%@”,obj);
[ self.xwCondition signal];
[ self.xwCondition unlock];
});
}
4.2 NSConditionLock
– ( void)lock11 {
NSConditionLock*conditionLock = [[ NSConditionLockalloc] init];
NSMutableArray*arrayM = [ NSMutableArrayarray];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[conditionLock lock];
for( inti = 0; i < 6; i++) {
[arrayM addObject:@(i)];
NSLog( @”異步下載第 %d 張圖片”,i);
sleep( 1);
if(arrayM.count == 4) {
[conditionLock unlockWithCondition: 4];
}
}
});
dispatch_async(dispatch_get_main_queue(), ^{
[conditionLock lockWhenCondition: 4];
NSLog( @”已經獲取到4張圖片->主線程渲染”);
[conditionLock unlock];
});
}
4.2 pthread POSIX Conditions
– ( void)lock12 {
__block pthread_mutex_t mutex;
__block pthread_cond_t condition;
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&condition, NULL);
NSMutableArray*arrayM = [ NSMutableArrayarray];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
pthread_mutex_lock(&mutex);
for( inti = 0; i < 6; i++) {
[arrayM addObject:@(i)];
NSLog( @”異步下載第 %d 張圖片”,i);
sleep( 1);
if(arrayM.count == 4) {
pthread_cond_signal(&condition);
}
}
});
dispatch_async(dispatch_get_main_queue(), ^{
pthread_cond_wait(&condition, &mutex);
NSLog( @”已經獲取到4張圖片->主線程渲染”);
pthread_mutex_unlock(&mutex);
});
}
5. 讀寫鎖
讀寫鎖實際是一種特殊的自旋鎖,它把對共享資源的訪問者劃分成讀者和寫者,讀者只對共享資源進行讀訪問,寫者則需要對共享資源進行寫操作。這種鎖相對於自旋鎖而言,能提高並發性,因為在多處理器系統中,它允許同時有多個讀者來訪問共享資源,最大可能的讀者數為實際的邏輯CPU數。寫者是排他性的,一個讀寫鎖同時只能有一個寫者或多個讀者(與CPU數相關),但不能同時既有讀者又有寫者。
5.1 dispatch_barrier_async / dispatch_barrier_sync
有一個需求,如圖:
任務1,2,3 均執行完畢執行任務0,然後執行任務4,5,6.
– ( void)lock13 {
dispatch_queue_tqueue = dispatch_queue_create( “com.qiuxuewei.brrier”, DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog( @”任務1 — %@”,[ NSThreadcurrentThread]);
});
dispatch_async(queue, ^{
NSLog( @”任務2 — %@”,[ NSThreadcurrentThread]);
});
dispatch_async(queue, ^{
NSLog( @”任務3 — %@”,[ NSThreadcurrentThread]);
});
dispatch_barrier_sync(queue, ^{
NSLog( @”任務0 — %@”,[ NSThreadcurrentThread]);
for( inti = 0; i < 100; i++) {
if(i % 30== 0) {
NSLog( @”任務0 — log:%d — %@”,i,[ NSThreadcurrentThread]);
}
}
});
NSLog( @”dispatch_barrier_sync down!!!”);
dispatch_async(queue, ^{
NSLog( @”任務4 — %@”,[ NSThreadcurrentThread]);
});
dispatch_async(queue, ^{
NSLog( @”任務5 — %@”,[ NSThreadcurrentThread]);
});
dispatch_async(queue, ^{
NSLog( @”任務6 — %@”,[ NSThreadcurrentThread]);
});
}
dispatch_barrier_async 和 dispatch_barrier_sync 的異同
共同點
等待它前面的執行完才執行自己的任務
等待自己任務執行結束才執行後面的任務
不同點
dispatch_barrier_async 將自己的任務插入到隊列之後會繼續將後面的操作插入到隊列,按照規則先執行前面隊列的任務,等自己隊列執行完畢,再執行後面隊列的任務
dispatch_barrier_sync 將自己的任務插入到隊列之後,先等待自己的任務執行完畢才會執行後面操作插入到隊列,再執行後面隊列的任務。
5.2 pthread
– ( void)lock14 {
__block pthread_rwlock_t rwlock;
pthread_rwlock_init(&rwlock, NULL);
__block NSMutableArray*arrayM = [ NSMutableArrayarray];
XWBlock writeBlock = ^ ( NSString*str) {
NSLog( @”開啟寫操作”);
pthread_rwlock_wrlock(&rwlock);
[arrayM addObject:str];
sleep( 2);
pthread_rwlock_unlock(&rwlock);
};
XWVoidBlock readBlock = ^ {
NSLog( @”開啟讀操作”);
pthread_rwlock_rdlock(&rwlock);
sleep( 1);
NSLog( @”讀取數據:%@”,arrayM);
pthread_rwlock_unlock(&rwlock);
};
for( inti = 0; i < 5; i++) {
dispatch_async(dispatch_get_global_queue( 0, 0), ^{
writeBlock([ NSStringstringWithFormat: @”%d”,i]);
});
}
for( inti = 0; i < 10; i++) {
dispatch_async(dispatch_get_global_queue( 0, 0), ^{
readBlock();
});
}
}
6.自旋鎖
boollock= false; // 一開始沒有鎖上,任何線程都可以申請鎖
do{
while( lock); // 如果 lock 為 true 就一直死循環,相當於申請鎖
lock= true; // 掛上鎖,這樣別的線程就無法獲得鎖
Critical section // 臨界區
lock= false; // 相當於釋放鎖,這樣別的線程可以進入臨界區
Reminder section // 不需要鎖保護的代碼
}
6.1 OSSpinLock
YYKit作者的文章 不再安全的 OSSpinLock
https://blog.ibireme.com/2016/01/16/spinlock_is_unsafe_in_ios/?utm_source=tuicool&utm_medium=referral有說到這個自旋鎖存在優先級反轉的問題。
6.2 os_unfair_lock
自旋鎖已經不再安全,然後蘋果又整出來個 os_unfair_lock_t ,這個鎖解決了優先級反轉的問題。
os_unfair_lock_tunfairLock;
unfairLock = &(OS_UNFAIR_LOCK_INIT);
os_unfair_lock_lock(unfairLock);
os_unfair_lock_unlock(unfairLock);
7. property – atomic / nonatomic
atomic 修飾的對象,系統會保證在其自動生成的 getter/setter 方法中的操作是完整的,不受其他線程的影響。例如 A 線程在執行 getter 方法時,B線程執行了 setter 方法,此時 A 線程依然會得到一個完整無損的對象。
atomic
默認修飾符 會保證CPU能在別的線程訪問這個屬性之前先執行完當前操作 讀寫速度慢 線程不安全 – 如果有另一個線程 D 同時在調[name release],那可能就會crash,因為 release 不受 getter/setter 操作的限制。也就是說,這個屬性只能說是讀/寫安全的,但並不是線程安全的,因為別的線程還能進行讀寫之外的其他操作。線程安全需要開發者自己來保證。
nonatomic
不默認 速度更快 線程不安全 如果兩個線程同時訪問會出現不可預料的結果。
8. Once 原子操作
8.1 GCD
– ( id)lock15 {
staticidshareInstance;
staticdispatch_once_tonceToken;
dispatch_once(&onceToken, ^{
if(!shareInstance) {
shareInstance = [[ NSObjectalloc] init];
}
});
returnshareInstance;
}
8.2 pthread
– ( void)lock16 {
pthread_once_tonce = PTHREAD_ONCE_INIT;
pthread_once(&once, lock16Func);
}
voidlock16Func(){
staticid shareInstance;
shareInstance = [[NSObject alloc] init];
}
長
按
關
註