iOS中多線程
首先看一道面試題
iOS中多線程有哪些實現方案?
技術方案 | 簡介 | 語言 | 線程生命周期 | 使用頻率 |
---|---|---|---|---|
pthread | 1. 一套通用的多線程API2. 跨平臺/可移植3. 使用難度大 | C | 程序員管理 | 幾乎不用 |
NSThread | 1.面向對象 2.簡單易用直接操作線程對象 | OC | 程序員管理 | 偶爾使用 |
GCD | 1.旨在替代NSThread等線程技術 2.充分利用設備的多核 | C | 自動管理 | 經常使用 |
NSOperation | 1.基于GCD 2.比GCD多了一些更實用的函數 | OC | 自動管理 | 經常使用 |
iOS中,多線程一般有三種方案GCD
、NSOperation
和NSThread
。
一、GCD
GCD相關問題一般分為三個方面:首先,同步/異步
和串行/并發
問題;其次,dispatch_barrier_async
異步柵欄調用,解決多讀單寫問題;最后,dispatch_group
使用和理解。
GCD中有2種用來執行任務的方式:同步和異步;同時還有兩種類型的隊列:并發和串行隊列。并發隊列讓多個任務并發執行,自動開啟多個線程同時執行任務。并發功能只在異步函數下才生效。
并發隊列 | 手動創建的串行隊列 | 主隊列 | |
---|---|---|---|
同步 | 沒有開辟新線程 串行執行 | 沒有開辟新線程 串行執行 | 沒有開辟新線程 串行執行 |
異步 | 有開辟新線程 并發執行 | 有開辟新線程 串行執行 | 沒有開辟新線程 串行執行 |
注意 :使用同步函數往當前串行隊列中添加任務,會卡主當前的串行隊列,產生死鎖。
1.1 同步/異步
和串行/并發
存在四種組合方案:
// 同步 + 串行
dispatch_sync(serial_queue, ^{
//任務
});
// 異步 + 串行
dispatch_async(serial_queue, ^{
//任務
});
// 同步 + 并發
dispatch_sync(concurrent_queue, ^{
//任務
});
// 異步 + 并發
dispatch_async(concurrent_queue, ^{
//任務
});
1.1.1 同步 + 串行
- (void)viewDidLoad {
dispatch_sync(dispatch_get_main_queue(), ^{
[self doSomething];
});
}
上面這段代碼,存在什么問題?
產生死鎖。隊列引起循環等待。因為,
viewDidLoad()
進入主隊列,執行過程中會將block
添加到主隊列中。viewDidLoad()
需要等待block
執行完成后才能結束,由于主隊列先進先出的block
需要viewDidLoad()
執行完畢才能執行。因此導致隊列循環等待的問題。上圖中,首先向主隊列中提交了一個 viewDidLoad 的任務,后續又提交了一個 Block 任務 。 現在分派 viewDidLoad
到主線程中去執行,在它執行過程中需要調用 Block ,等 Block 同步調用完成之后,這個 viewDidLoad 才能繼續向下走,所以viewDidLoad 的調用結束依賴于 Block 方法執行完。 而隊列是遵循先進先出(FIFO)原則的,Block 要想執行,就必須等待 viewDidLoad 調用完成。 由此就產生了隊列的循環等待,造成死鎖.
上面的問題理解,在來看一個問題。
- (void)viewDidLoad {
dispatch_sync(serial_queue, ^{
[self doSomething];
});
}
上面的代碼,有什么問題?
沒有問題。這里是將
block
添加到單獨的串行隊列。viewDidLoad()
在主隊列中在主線程中執行,在其執行過程中調用block
添加到串行隊列中,在主線程中執行。viewDidLoad 在主隊列中,提交到主線程處理,在 viewDidLoad方法運行到某一時刻的時候,會提交一個任務到串行隊列上。串行隊列同步提交一個 Block任務,因為是同步的(同步提交就是在當前線程執行),所以串行隊列中的任務也是提交到主線程中執行,當串行隊列這個任務在主線程處理完成之后,再繼續處理viewDidLoad 后續的代碼邏輯.。同步方式提交任務,無論在串行隊列還是并發隊列都會在當前線程中執行。
1.1.2 同步 + 并發
int main(int argc, const char * argv[]) {
@autoreleasepool {
dispatch_queue_t global_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"1");
dispatch_sync(global_queue, ^{
NSLog(@"2");
dispatch_sync(global_queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
return 0;
}
上面這段代碼的輸出結果?
輸出結果:12345。同步方式提交任務,無論在串行隊列還是并發隊列都會在當前線程中執行。因為是同步,所以都是在主線程執行。globaQueue 是并發隊列,所以不會造成死鎖。如果將 倆個globaQueue 都換成串行隊列,就會造成死鎖.
1.1.3 異步 + 串行
-(void)viewDidLoad
{
dispatch_async(dispatch_get_main_queue(), ^{
[self doSomething];
});
}
1.1.4 異步 + 并發
面試題
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t global_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(global_queue, ^{
NSLog(@"1");
[self performSelector:@selector(printLog) withObject:nil afterDelay:0];
NSLog(@"3");
});
}
- (void)printLog {
NSLog(@"2");
}
上述代碼執行的結果?
結果:13。提交異步任務到并發隊列,任務調用了
performSelector:withObject:afterDelay:
。由于GCD提交的任務是在某個子線程中執行,子線程沒有RunLoop。由于performSelector:withObject:afterDelay:
需要RunLoop才可生效,所以方法不執行。這個問題,考察performSelector:withObject:afterDelay:
內部實現。
分析首先這是一個異步方式分配到全局并發隊列當中的,這個 Block 會在 GCD 底層的線程池當中的某一個線程中執行。 GCD 底層所分派的這些線程默認是不開啟 Runloop 的,而 performSelector 方法是需要創建相應的任務提交到 Runloop 上,所以在GCD 底層線程沒有 Runloop 的情況下,這個方法就會失效 .也就是說 performSelector要想能夠有效執行必須是它方法調用所屬的當前線程有 Runloop 的。
任務和隊列示例代碼:任務和隊列Demo包含面試題講解
二、多讀單寫解決方案
pthread_rwlock: 讀寫鎖
dispatch_barrier_async() 異步柵欄調用
怎么利用GCD實現多讀單寫?或者如何實現多讀單寫?
2.1 什么是多讀單寫?
讀者和讀者,并發。 讀者和寫者,互斥。 寫者與寫者,互斥。
2.2 解決方法
dispatch_async(global_queue, ^{
NSLog(@"讀取1");
});
dispatch_async(global_queue, ^{
NSLog(@"讀取2");
});
dispatch_barrier_async(global_queue, ^{
NSLog(@"寫入1");
});
dispatch_async(global_queue, ^{
NSLog(@"讀取3");
});
dispatch_async(global_queue, ^{
NSLog(@"讀取4");
});
dispatch_barrier_async
函數會等待追加到并發隊列上的并行執行的處理全部結束之后,在將指定的處理追加到該并發隊列中。然后等dispatch_barrier_async
函數追加的處理執行完畢后,并發隊列才恢復為一般的動作,追加到并發隊列的處理又開始并行執行。
三、dispatch_group_async()
面試題:
如何用GCD 實現:A、B、C三個任務并發,完成后執行任務D?
實現追加到并發隊列中的多個任務全部結束后再執行想執行的任務。無論向什么樣的隊列中追加處理,使用DispatchGroup都可監視這些處理執行的結束。一旦檢測到所有處理執行結束,該Dispatch Group與隊列相同。
dispatch_group_async() 同dispatch_async()函數相同,都追加Block到指定的DispatchQueue中。當組中所有任務都執行完成后,dispatch_group_notify()執行Block中的內容。
示例代碼:
// dispatch_group_notify
- (void)test {
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("com.lqq.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任務1 - %@", [NSThread currentThread]);
}
});
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任務2 - %@", [NSThread currentThread]);
}
});
//--------------示例1------------------- // 寫法一, 等上面的任務執行完成后,才會在主隊列中執行任務3
// dispatch_group_notify(group, queue, ^{
// dispatch_async(dispatch_get_main_queue(), ^{
// for (int i = 0; i < 5; i++) {
// NSLog(@"任務3 - %@", [NSThread currentThread]);
// }
// });
// });
//寫法二:直接在主隊列中執行
// dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// for (int i = 0; i < 5; i++) {
// NSLog(@"任務3 - %@", [NSThread currentThread]);
// }
// });
//--------------示例2------------------- // 如果有多個notify會怎么執行呢?
dispatch_group_notify(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任務3 - %@", [NSThread currentThread]);
}
});
dispatch_group_notify(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任務4 - %@", [NSThread currentThread]);
}
});
// 任務3和任務4是交替執行的
}
另外,也可以使用dispatch_group_wait
,如下:
// 監控任務是否完成,當完成時會返回0,不完成一直等待。
long result = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
if (result == 0) {
NSLog(@"全部任務執行完成");
}
線程組示例代碼:線程組API使用Demo
四、NSOperation
需要和NSOperationQueue配合使用來實現多線程。優勢和特點:添加任務依賴、任務執行狀態控制、控制最大并發量。
任務狀態的控制
isReady
isExecuting
isFinished
isCanceled
如果重寫main方法,底層控制變更任務執行完成狀態,以及任務退出。
如果重寫start方法,自行控制任務狀態。
- (void) start
{
NSAutoreleasePool *pool = [NSAutoreleasePool new];
double prio = [NSThread threadPriority];
AUTORELEASE(RETAIN(self)); // Make sure we exist while running.
[internal->lock lock];
NS_DURING
{
if (YES == [self isExecuting])
{
[NSException raise: NSInvalidArgumentException
format: @"[%@-%@] called on executing operation",
NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
}
if (YES == [self isFinished])
{
[NSException raise: NSInvalidArgumentException
format: @"[%@-%@] called on finished operation",
NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
}
if (NO == [self isReady])
{
[NSException raise: NSInvalidArgumentException
format: @"[%@-%@] called on operation which is not ready",
NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
}
if (NO == internal->executing)
{
[self willChangeValueForKey: @"isExecuting"];
internal->executing = YES;
[self didChangeValueForKey: @"isExecuting"];
}
}
NS_HANDLER
{
[internal->lock unlock];
[localException raise];
}
NS_ENDHANDLER
[internal->lock unlock];
NS_DURING
{
if (NO == [self isCancelled])
{
[NSThread setThreadPriority: internal->threadPriority];
[self main];
}
}
NS_HANDLER
{
[NSThread setThreadPriority: prio];
[localException raise];
}
NS_ENDHANDLER;
[self _finish];
[pool release];
}
面試題
系統是怎樣移除一個isFinished=YES的NSOperation的?答案:KVO
五、NSThread
啟動流程
start() -> 創建pthread -> main() ->[target performSelector:selector] -> exit()
- (void) start
{
pthread_attr_t attr;
if (_active == YES)
{
[NSException raise: NSInternalInconsistencyException
format: @"[%@-%@] called on active thread",
NSStringFromClass([self class]),
NSStringFromSelector(_cmd)];
}
if (_cancelled == YES)
{
[NSException raise: NSInternalInconsistencyException
format: @"[%@-%@] called on cancelled thread",
NSStringFromClass([self class]),
NSStringFromSelector(_cmd)];
}
if (_finished == YES)
{
[NSException raise: NSInternalInconsistencyException
format: @"[%@-%@] called on finished thread",
NSStringFromClass([self class]),
NSStringFromSelector(_cmd)];
}
/* Make sure the notification is posted BEFORE the new thread starts.
*/
gnustep_base_thread_callback();
/* The thread must persist until it finishes executing.
*/
RETAIN(self);
/* Mark the thread as active while it's running.
*/
_active = YES;
errno = 0;
pthread_attr_init(&attr);
/* Create this thread detached, because we never use the return state from
* threads.
*/
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
/* Set the stack size when the thread is created. Unlike the old setrlimit
* code, this actually works.
*/
if (_stackSize > 0)
{
pthread_attr_setstacksize(&attr, _stackSize);
}
if (pthread_create(&pthreadID, &attr, nsthreadLauncher, self))
{
DESTROY(self);
[NSException raise: NSInternalInconsistencyException
format: @"Unable to detach thread (last error %@)",
[NSError _last]];
}
}
/**
* Trampoline function called to launch the thread
*/
static void *
nsthreadLauncher(void *thread)
{
NSThread *t = (NSThread*)thread;
setThreadForCurrentThread(t);
/*
* Let observers know a new thread is starting.
*/
if (nc == nil)
{
nc = RETAIN([NSNotificationCenter defaultCenter]);
}
[nc postNotificationName: NSThreadDidStartNotification
object: t
userInfo: nil];
[t _setName: [t name]];
[t main];
[NSThread exit];
// Not reached
return NULL;
}
- (void) main
{
if (_active == NO)
{
[NSException raise: NSInternalInconsistencyException
format: @"[%@-%@] called on inactive thread",
NSStringFromClass([self class]),
NSStringFromSelector(_cmd)];
}
[_target performSelector: _selector withObject: _arg];
}
如何實現常駐進程? 通過使用NSThread和RunLoop實現。
在iOS多線程中,經常會出現資源競爭和死鎖的問題。本節將學習iOS中不同的鎖。
線程同步方案
常見的兩個問題:多線程買票和存取錢問題。
示例:存取錢問題
// 示例:存取錢問題
- (void)moneyTest {
self.moneyCount = 100;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self saveMoney];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self takeMoney];
}
});
}
- (void)saveMoney {
int oldCount = self.moneyCount;
sleep(0.2);
oldCount += 50;
self.moneyCount = oldCount;
NSLog(@"存50,還剩%d錢", self.moneyCount);
}
?
- (void)takeMoney {
int oldCount = self.moneyCount;
sleep(0.2);
oldCount -= 20;
self.moneyCount = oldCount;
NSLog(@"取20,還剩%d錢", self.moneyCount);
}
示例:賣票問題
// 示例:買票
- (void)sellTest {
self.count = 15;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self printTest2];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self printTest2];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self printTest2];
}
});
}
- (void)printTest2 {
NSInteger oldCount = self.count;
sleep(0.2);
oldCount --;
self.count = oldCount;
NSLog(@"還剩%ld張票 - %@", (long)oldCount, [NSThread currentThread]);
}
解決上面這種資源共享問題,就需要使用線程同步技術。線程同步技術的核心是:鎖。下面學習iOS中不同鎖的使用,比較不同鎖之間的優缺點。
示例代碼:演示購票和存取錢問題:Demo
iOS當中有哪些鎖?
@synchronized 常用于單例
atomic 原子性
OSSpinLock 自旋鎖
NSRecursiveLock 遞歸鎖
NSLock
dispatch_semaphore_t 信號量
NSCondition 條件
NSConditionLock 條件鎖</pre>
簡介:
@synchronized
使用場景:一般在創建單例對象時使用,保證對象在多線程中是唯一的。atomic
屬性關鍵字原子性,保證賦值操作是線程安全的,讀取操作不能保證線程安全。OSSpinLock
自旋鎖。特點:循環等待訪問,不釋放當前資源。常用于輕量級數據訪問,簡單的int值+1/-1操作。 一般用于引用計數操作NSLock
某個線程A調用lock方法。這樣,NSLock將被上鎖。可以執行“關鍵部分”,完成后,調用unlock方法。如果,在線程A 調用unlock方法之前,另一個線程B調用了同一鎖對象的lock方法。那么,線程B只有等待。直到線程A調用了unlock。
[lock lock]; //加鎖
// 關鍵部分代碼{}
[lock unlock]; // 解鎖
NSLock使用的過程要注意因重復上鎖產生的死鎖
NSRecursiveLock
遞歸鎖,特點:遞歸鎖在被同一線程重復獲取時不會產生死鎖。dispatch_semaphore_t
信號量
// 創建信號量結構體對象,含有一個int成員
dispatch_semaphore_create(1);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);// 先對value減一,如果小于零表示沒有資源可以訪問。通過主動行為進行阻塞。
dispatch_semaphore_signal(semaphore);// value加1,小于等零表示有隊列在排隊,通過被動行為進行喚醒。
OSSpinLock
自旋鎖,等待鎖的線程會處于忙等狀態,一直占用著CPU資源。
常用API:
導入頭文件
#import <libkern/OSAtomic.h>
// 初始化
OSSpinLock lock = OS_SPINLOCK_INIT;
// 嘗試加鎖
OSSpinLockTry(&lock);
// 加鎖
OSSpinLockLock(&lock);
// 解鎖
OSSpinLockUnlock(&lock);
使用OSSpinLock
解決賣票問題
// 自旋鎖:#import <libkern/OSAtomic.h>
// 定義一個全局的自旋鎖對象 lock 。
- (void)printTest2 {
// 加鎖
OSSpinLockLock(&_lock);
NSInteger oldCount = self.count;
sleep(0.2);
oldCount --;
self.count = oldCount;
NSLog(@"還剩%ld張票 - %@", (long)oldCount, [NSThread currentThread]);
// 解鎖
OSSpinLockUnlock(&_lock);
}
使用OSSpinLock
解決存取錢問題
- (void)saveMoney {
OSSpinLockLock(&_moneyLock);
int oldCount = self.moneyCount;
sleep(0.2);
oldCount += 50;
self.moneyCount = oldCount;
NSLog(@"存50,還剩%d錢", self.moneyCount);
OSSpinLockUnlock(&_moneyLock);
}
- (void)takeMoney {
OSSpinLockLock(&_moneyLock);
int oldCount = self.moneyCount;
sleep(0.2);
oldCount -= 20;
self.moneyCount = oldCount;
NSLog(@"取20,還剩%d錢", self.moneyCount);
OSSpinLockUnlock(&_moneyLock);
}
注意:賣票和取錢不要共用一把鎖。這里創建了兩把鎖
sellLock
和moneyLock
。
自旋鎖現在不再安全,因為可能出現優先級反轉問題。如果等待鎖的線程優先級較高,他會一直占用CPU資源,優先級低的線程就無法獲取CPU資源完成任務并釋放鎖。可以查看這篇文章不再安全的OSSpinLock。
本節示例代碼:線程同步解決方案Demo
os_unfair_lock
自旋鎖已經不再安全,存在優先級反轉問題。蘋果在iOS10開始使用os_unfair_lock
取代了OSSpinLock
。從底層調用來看,自旋鎖和os_unfair_lock
的區別,前者等待線程處于忙等,而后者等待線程處于休眠狀態。
常用API:
導入頭文件
#import <os/lock.h>
// 初始化
os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
// 嘗試加鎖
os_unfair_lock_trylock(&_lock);
// 加鎖
os_unfair_lock_lock(&_lock);
// 解鎖
os_unfair_lock_unlock(&_lock);
pthread_mutex
互斥鎖,等待鎖的線程處于休眠狀態。
常用API:
// 頭文件 #import <pthread.h>
- (void)__initLock:(pthread_mutex_t *)lock {
// 初始化屬性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
// 設置為普通鎖,PTHREAD_MUTEX_RECURSIVE表示遞歸鎖
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
// 初始化鎖
pthread_mutex_init(lock, &attr);
// 銷毀屬性
pthread_mutexattr_destroy(&attr);
}
// 加鎖
pthread_mutex_lock(&lock);
// 解鎖
pthread_mutex_unlock(&lock);
// 初始化條件
pthread_cond_init(&cond, NULL)
// 等待條件(進入休眠,放開鎖;被喚醒后,會再次加鎖)
pthread_cond_wait(&cond, &lock);
// 激活一個等待該條件的線程
pthread_cond_signal(&cond);
// 激活所有等待該條件的線程
pthread_cond_broadcast(&cond);
// 銷毀資源
pthread_mutex_destory(&lock);
pthread_cond_destory(&cond);
其中PTHREAD_MUTEX_DEFAULT
設置的是鎖的類型,還有另一種類型PTHREAD_MUTEX_RECURSIVE
表示遞歸鎖。遞歸鎖允許同一個線程對一把鎖進行重復加鎖。
NSLock&NSRecursiveLock&NSCondition
NSLock
是對mutex
普通鎖的封裝。
@protocol NSLocking
- (void)lock;
- (void)unlock;
@end
?
@interface NSLock : NSObject <NSLocking> {
- (BOOL)tryLock; // 嘗試加鎖
- (BOOL)lockBeforeDate:(NSDate *)limit; //在時間之前獲取鎖并返回,YES表示成功。
}
@end
NSRecursiveLock
是對mutex
遞歸鎖的封裝,API同NSLock
相似。
NSCondition
是對mutex
條件的封裝。
@protocol NSLocking
- (void)lock;
- (void)unlock;
@end
@interface NSCondition : NSObject <NSLocking> {
- (void)wait; // 等待
- (BOOL)waitUntilDate:(NSDate *)limit; // 等待某一個時間段
- (void)signal; // 喚醒
- (void)broadcast; // 喚醒所有睡眠線程
}
以上可以查看pthread_mutex
使用。
atomic
atomic
用于保證屬性setter
和getter
的原子性操作,相當于對setter
和getter
內部加了同步鎖。它并不能保證使用屬性的使用過程是線程安全的。
NSConditionLock
NSConditionLock
是對NSCondition
的進一步封裝。可以設置具體的條件值。
// 遵循NSLocking協議。
@interface NSConditionLock : NSObject <NSLocking> {
- (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;
@end
示例代碼:
// 刪除
- (void)__one {
?
// 當鎖內部條件值為1時,加鎖。
// [self.condition lockWhenCondition:1];
[self.condition lock]; // 直接使用lock也可以
sleep(1);
NSLog(@"%s ①", __func__);
[self.condition unlockWithCondition:2]; // 解鎖,并且條件設置為2
}
// 添加
- (void)__two {
[self.condition lockWhenCondition:2]; //條件值為2時,加鎖。
sleep(1);
NSLog(@"%s ②", __func__);
[self.condition unlockWithCondition:3];
}
?
// 添加
- (void)__three {
[self.condition lockWhenCondition:3]; //條件值為3時,加鎖。
sleep(1);
NSLog(@"%s ③", __func__);
[self.condition unlock];
}
?
- (void)otherTest {
// ①
[[[NSThread alloc] initWithTarget:self selector:@selector(__one) object:nil] start];
// ②
[[[NSThread alloc] initWithTarget:self selector:@selector(__two) object:nil] start];
// ③
[[[NSThread alloc] initWithTarget:self selector:@selector(__three) object:nil] start];
// 通過設置條件值,可以決定線程的執行順序。
}
輸出結果:
-[LENSConditionLock __one] ①
-[LENSConditionLock __two] ②
-[LENSConditionLock __three] ③
信號量
常用API:
// 初始化
dispatch_semaphore_t semaphore = dispatch_semaphore_create(5);
// 如果信號量的值<=0,當前線程就會進入休眠等待,直到信號量的值>0
// 如果信號量的值>0,就減1,然后往下執行后面的代碼
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
// 讓信號量的值增加1,信號量值不等于零時,前面的等待的代碼會執行。
dispatch_semaphore_signal(self.semaphore);
dispatch_semaphore
信號量的初始值,控制線程的最大并發訪問數量。 信號量的初始值為1,代表同時只允許1條線程訪問資源,保證線程同步。
示例代碼:
// 設置信號量初始值為5。
- (void)otherTest {
for (int i = 0; i < 20; i ++) {
[[[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil] start];
}
}
- (void)test {
// 如果信號量的值<=0,當前線程就會進入休眠等待,直到信號量的值>0
// 如果信號量的值>0,就減1,然后往下執行后面的代碼
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
sleep(2);
NSLog(@"test - %@", [NSThread currentThread]);
// 讓信號量的值增加1
dispatch_semaphore_signal(self.semaphore);
}
@synchronized
@synchronized
是對mutex
遞歸鎖的封裝。 不推薦使用,性能比較差。
// 源碼:objc4中的objc-sync.mm
@synchronized (obj) {
}
性能比較
不再安全的OSSpinLock中對比了不同鎖的性能。 推薦使用dispatch_semaphore
和pthread_mutex
兩個。因為OSSpinLock
性能最好但是不安全,os_unfair_lock
在iOS10才出現低版本不支持不推薦。
自旋鎖、互斥鎖的選擇
自旋鎖預計線程等待鎖的時間很短,加鎖經常被調用但競爭情況很少出現。常用于多核處理器。 互斥鎖預計等待鎖的時間較長,單核處理器。臨界區有IO操作,例如文件讀寫。
示例代碼:鎖實例代碼-Github
小結
怎樣用GCD實現多讀單寫?
iOS提供幾種多線程技術各自的特點?
NSOperation對象在Finished之后是怎樣從隊列中移除的?
你都用過哪些鎖?結合實際談談你是怎樣使用的?
參考
小碼哥底層班視頻 正確使用多線程同步鎖@synchronized() 深入理解iOS開發中的鎖 Object-C 多線程中鎖的使用-NSLock
附錄
GCD API介紹:
dispatch_queue_create(名稱,類型)
類型為NULL時,為串行隊列;為DISPATCH_QUEUE_CONCUREENT,為并行隊列.。
?
需要手動release釋放隊列
dispatch_release(隊列)
?
主隊列,串行
dispatch_get_main_queue()
dispatch_get_global_queue(優先級,0)
?
優先級有四種情況:
高,默認,低,后臺
為自己創建的隊列設置優先級
?
dispatch_set_target_queue(自定義隊列,其他已知優先級的隊列)
?
dispatch_after(時間,隊列,Block)
時間:dispatch_time_t
?
dispatch_group 監聽所有任務結束。
?
dispatch_group_create() //創建一個隊列組,需要手動release
dispatch_group_async(組,隊列,block)
dispatch_group_notify(組,隊列,block) // 所有任務都結束后調用
也可以使用dispatch_group_wait()
?
dispatch_barrier_async() 一般用于數據庫操作和文件讀寫。
?
同步任務
dispatch_sync(隊列,block)
?
異步任務
dispatch_async(隊列,block)
?
指定執行次數
dispatch_apply(次數,隊列,block)
掛起
dispatch_suspend(隊列)
?
恢復
dispatch_resume(隊列)
?
信號量
Dispatch Semaphore是持有計數的信號,該計數是多線程編程中的計數類型信號。
?
dispatch_semaphore_create(技術值)
dispatch_semaphore_wait(semaphore, 時間)
dispatch_semaphore_signal(semaphore)
dispatch_reliease(semaphore)
?
// 示例
// 創建一個對象,默認的計數值為0,等待。當計數值大于1或者等于1時,減去1而不等待。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
// 發送計數值加一信號
dispatch_semaphore_signal(semaphore);
// 等待計數值變化,第二個參數是等待時間,這里是永久等待。
// 當計數值大于0時,返回0。等于0不會返回任何值。
long result = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
?
if (result == 0) {
NSLog(@"執行排他性操作");
}
dispatch_once函數是保證在應用程序執行中只執行一次的API。
dispatch I/O 分割讀取,提高讀取效率。