一、iOS多線程
iOS多線程開發(fā)有三種方式:
- NSThread
- NSOperation
- GCD
iOS在每個(gè)進(jìn)程啟動(dòng)后都會(huì)創(chuàng)建一個(gè)主線程,更新UI要在主線程上,所以也稱為UI線程,是其他線程的父線程。
線程和進(jìn)程的區(qū)別傻傻分不清楚:
- 線程(thread):用于指代獨(dú)立執(zhí)行的代碼段。
- 進(jìn)程(process):用于指代一個(gè)正在運(yùn)行的可執(zhí)行程序,它可以包含多個(gè)線程。

二、NSThread
NSThreadhi輕量級(jí)的多線程開發(fā),需要自己管理線程生命周期
創(chuàng)建線程主要實(shí)現(xiàn)方法:
/* 直接將操作添加到新線程中并執(zhí)行,該方法無(wú)法拿到線程對(duì)象 */
+ (void)detachNewThreadSelector:(SEL)selector /* 方法名 */
toTarget:(id)target /* 調(diào)用對(duì)象 */
withObject:(id)argument; /* 參數(shù) */
/* 創(chuàng)建線程對(duì)象,初始化線程任務(wù),調(diào)用start方法啟動(dòng)線程 */
- (instancetype)initWithTarget:(id)target /* 調(diào)用對(duì)象 */
selector:(SEL)selector /* 方法名 */
object:(id)argument;/* 參數(shù) */
實(shí)際使用:
/* 創(chuàng)建一個(gè)線程,初始化任務(wù),創(chuàng)建線程并不會(huì)啟動(dòng)線程 */
NSThread *thread = [[NSThread alloc] initWithTarget:self
selector:@selector(loadImage)
object:nil];
[thread start];//啟動(dòng)線程
/* 直接將操作添加到新線程并啟動(dòng)線程 */
[NSThread detachNewThreadSelector:@selector(loadImage)
toTarget:self
withObject:nil];
- 每個(gè)線程的實(shí)際執(zhí)行順序并不一定按啟動(dòng)順序執(zhí)行
- 如果是單核CPU,多線程是并發(fā),分時(shí)間片切換執(zhí)行不同線程,多核CPU的多線程才是真正的并行運(yùn)算。
線程狀態(tài)分為:
- isExecuting(正在執(zhí)行)
- isFinished(已經(jīng)完成)
- isCancellled(已經(jīng)取消)
下面是常用方法來(lái)控制線程:
/* 讓線程休眠 */
+ (void)sleepUntilDate:(NSDate *)date;/* 讓當(dāng)前執(zhí)行線程休眠到某個(gè)時(shí)間 */
+ (void)sleepForTimeInterval:(NSTimeInterval)time;/* 讓當(dāng)前執(zhí)行線程休眠固定多少秒 */
/* 終止線程 */
+ (void)exit;
/* 停止線程,注意在主線程中調(diào)用僅僅只是設(shè)置線程狀態(tài),不會(huì)立刻停止線程 */
- (void)cancel;
實(shí)例:
NSThread *thread = threads[i];
//判斷線程是否完成,如果沒(méi)有完成則設(shè)置為取消狀態(tài)
//注意設(shè)置為取消狀態(tài)僅僅是改變了線程狀態(tài)而言,并不能立刻終止線程
if ( !thread.isFinished ) {
[thread cancel];
}
//線程休眠2秒
[NSThread sleepForTimeInterval:2.0];
我們知道了控制單個(gè)線程,怎么在線程之間進(jìn)行通信呢?
下面是線程間通信的常用方法:
/* 在后臺(tái)執(zhí)行一個(gè)操作,本質(zhì)就是重新創(chuàng)建一個(gè)線程執(zhí)行當(dāng)前方法 */
- (void)performSelectorInBackground:(SEL)aSelector
withObject:(id)arg;
/* 在指定的線程上執(zhí)行一個(gè)方法,需要用戶創(chuàng)建一個(gè)線程對(duì)象 */
- (void)performSelector:(SEL)aSelector
onThread:(NSThread *)thr
withObject:(id)arg
waitUntilDone:(BOOL)wait;
/* 在主線程上執(zhí)行一個(gè)方法 */
- (void)performSelectorOnMainThread:(SEL)aSelector
withObject:(id)arg
waitUntilDone:(BOOL)wait;
三、NSOperation
只要將NSOperation放入NSOperationQueue線程隊(duì)列中,就會(huì)啟動(dòng)執(zhí)行。
NSOperationQueue負(fù)責(zé)管理、執(zhí)行所有的NSOperation,這樣更加容易管理線程總數(shù)和控制線程之間的依賴。
NSOperation是個(gè)基類,我們直接使用的是它的子類:
- NSInvocationOperation:調(diào)用方法SEL的方式執(zhí)行線程可以使用它
- NSBlockOperation:調(diào)用Block的方式執(zhí)行線程可以使用它
下面是使用實(shí)例:
//創(chuàng)建Invocation線程
NSInvocationOperation *invocationOperation =
[[NSInvocationOperation alloc] initWithTarget:self
selector:@selector(loadImage)
object:nil];
//注意如果直接調(diào)用start方法,則此操作會(huì)在主線程中調(diào)用
// [invocationOperation start];//一般不會(huì)這么操作,而是添加到NSOperationQueue中
//創(chuàng)建線程隊(duì)列
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
operationQueue.maxConcurrentOperationCount = 5;//設(shè)置最大并發(fā)線程數(shù)
//注意添加到線程隊(duì)列后,隊(duì)列里的線程就會(huì)開始執(zhí)行
[operationQueue addOperation:invocationOperation];
//創(chuàng)建Block線程
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
[self loadImage:[NSNumber numberWithInt:0]];
}];
//添加進(jìn)線程隊(duì)列
[operationQueue addOperation:blockOperation];
如果覺(jué)得添加NSBlockOperation線程麻煩,還有個(gè)簡(jiǎn)單的方法,是NSOperationQueue的對(duì)象方法:
// 快捷添加NSBlockOperation
[operationQueue addOperationWithBlock:^{
[self loadImage:[NSNumber numberWithInt:0]];
}];
我說(shuō)過(guò)NSOperationQueue可以控制線程之間的依賴,這是怎么一回事呢?想象一種場(chǎng)景,加載多個(gè)圖片,但我想最后一張圖片一定要先加載,其他圖片加載的前提就是最后一張圖片要加載完成,這時(shí)候就可以使用依賴了。非常簡(jiǎn)單。
多圖片加載實(shí)例:
- (void)loadImageWithMultiThread{
int count = ROW_COUNT*COLUMN_COUNT;
//創(chuàng)建線程隊(duì)列
NSOperationQueue *operationQueue = [[NSOperationQueue alloc]init];
operationQueue.maxConcurrentOperationCount = 5;//設(shè)置最大并發(fā)線程數(shù)
//創(chuàng)建加載最后一張圖片的線程
NSBlockOperation *lastBlockOperation = [NSBlockOperation blockOperationWithBlock:^{
[self loadImage:[NSNumber numberWithInt:(count-1)]];
}];
//創(chuàng)建多個(gè)線程用于下載其他圖片
for (int i=0; i<count-1; ++i) {
//創(chuàng)建多線程操作
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
[self loadImage:[NSNumber numberWithInt:i]];
}];
//設(shè)置依賴操作為最后一張圖片加載操作,只有最后一張圖片加載完成,其他圖片才開始陸續(xù)加載
[blockOperation addDependency:lastBlockOperation];
[operationQueue addOperation:blockOperation];
}
//將最后一個(gè)圖片的加載線程加入線程隊(duì)列
[operationQueue addOperation:lastBlockOperation];
}

四、GCD
GCD中也有一個(gè)類似于NSOperationQueue的隊(duì)列,GCD統(tǒng)一管理整個(gè)隊(duì)列中的任務(wù),GCD是C語(yǔ)言下的框架。
GCD中的隊(duì)列分為并行隊(duì)列和串行隊(duì)列:
- 串行隊(duì)列(serial):只有一個(gè)線程,加入到隊(duì)列中的操作按添加順序依次執(zhí)行。
- 并發(fā)隊(duì)列(concurrent):有多個(gè)線程,操作進(jìn)來(lái)之后它會(huì)將這些隊(duì)列安排在可用的處理器上,同時(shí)保證先進(jìn)來(lái)的任務(wù)優(yōu)先處理,但不是順序的。
其實(shí)在GCD中還有一個(gè)特殊隊(duì)列就是主隊(duì)列,用來(lái)執(zhí)行主線程上的操作任務(wù)。
GCD執(zhí)行方式也分為異步執(zhí)行和同步執(zhí)行:
- dispatch_async(異步執(zhí)行) :不管隊(duì)列中的任務(wù)執(zhí)行完還是沒(méi)執(zhí)行完,直接將任務(wù)追加到隊(duì)列
- dispatch_sync(同步執(zhí)行) : 等隊(duì)列中的任務(wù)執(zhí)行完,再將任務(wù)追加到隊(duì)列
串行隊(duì)列和并發(fā)隊(duì)列的創(chuàng)建:
/*創(chuàng)建一個(gè)隊(duì)列
第一個(gè)參數(shù):隊(duì)列名稱
第二個(gè)參數(shù):隊(duì)列類型,DISPATCH_QUEUE_SERIAL串行,DISPATCH_QUEUE_CONCURRENT并發(fā)
注意:GCD的queue不是指針類型
*/
dispatch_queue_t serialQueue = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t concurrentQueue = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
/* 使用dispatch_get_global_queue() 方法取得一個(gè)全局的并發(fā)隊(duì)列 */
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
隊(duì)列的異步執(zhí)行和同步執(zhí)行:
//異步執(zhí)行隊(duì)列任務(wù),第一個(gè)參數(shù)是隊(duì)列,第二個(gè)參數(shù)是任務(wù)Block
dispatch_async(queue, ^{
[self loadImage:[NSNumber numberWithInt:i]];
});
//同步執(zhí)行隊(duì)列任務(wù),第一個(gè)參數(shù)是隊(duì)列,第二個(gè)參數(shù)是任務(wù)Block
dispatch_sync(queue, ^{
[self loadImage:[NSNumber numberWithInt:i]];
});
- 在GDC中一個(gè)操作是多線程執(zhí)行還是單線程執(zhí)行,取決于當(dāng)前隊(duì)列類型和執(zhí)行方法,只有隊(duì)列類型為并行隊(duì)列并且使用異步方法執(zhí)行時(shí)才能在多個(gè)線程中并發(fā)執(zhí)行。
- 串行隊(duì)列可以按順序執(zhí)行,并行隊(duì)列的異步方法無(wú)法確定執(zhí)行順序。
- 更新UI界面最好采用同步方法,其他操作采用異步方法。
GCD的其他任務(wù)執(zhí)行方法:
/* 重復(fù)執(zhí)行某個(gè)任務(wù),為了不阻塞線程可以使用dispatch_async()包裝一下再執(zhí)行 */
dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t));
/* 單次執(zhí)行一個(gè)任務(wù),此方法中的任務(wù)只會(huì)執(zhí)行一次,重復(fù)調(diào)用也沒(méi)辦法重復(fù)執(zhí)行(單例模式中常用此方法)*/
dispatch_once(dispatch_once_t *predicate, dispatch_block_t block);
/* 常用:延遲delayInSeconds秒后在隊(duì)列queue執(zhí)行block操作 */
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)), dispatch_queue_t queue, dispatch_block_t block);
五、線程同步
為什么需要線程同步?因?yàn)橐鉀Q多線程的資源搶奪的問(wèn)題
1.NSLock同步鎖
//初始化鎖對(duì)象
NSLock *myLock = [[NSLock alloc] init];
//加鎖
[myLock lock];//加鎖后,下面的代碼只能有一個(gè)線程進(jìn)入執(zhí)行
if (_imageNames.count > 0) {
name = [_imageNames lastObject];
[_imageNames removeObject:name];
}
//使用完解鎖
[myLock unlock];
2.@synchronized代碼塊
//線程同步
@synchronized(self){
if (_imageNames.count > 0) {
name = [_imageNames lastObject];
[_imageNames removeObject:name];
}
}
線程同步就不細(xì)講了,這是一個(gè)大塊知識(shí)。