概覽
大家都知道床三,在開發(fā)過程中應(yīng)該盡可能減少用戶等待時間,讓程序盡可能快的完成運算月趟〉坪可是無論是哪種語言開發(fā)的程序最終往往轉(zhuǎn)換成匯編語言進而解釋成機器碼來執(zhí)行。但是機器碼是按順序執(zhí)行的孝宗,一個復(fù)雜的多步操作只能一步步按順序逐個執(zhí)行穷躁。改變這種狀況可以從兩個角度出發(fā):對于單核處理器,可以將多個步驟放到不同的線程因妇,這樣一來用戶完成UI操作后其他后續(xù)任務(wù)在其他線程中问潭,當CPU空閑時會繼續(xù)執(zhí)行,而此時對于用戶而言可以繼續(xù)進行其他操作婚被;對于多核處理器狡忙,如果用戶在UI線程中完成某個操作之后,其他后續(xù)操作在別的線程中繼續(xù)執(zhí)行址芯,用戶同樣可以繼續(xù)進行其他UI操作灾茁,與此同時前一個操作的后續(xù)任務(wù)可以分散到多個空閑CPU中繼續(xù)執(zhí)行(當然具體調(diào)度順序要根據(jù)程序設(shè)計而定),及解決了線程阻塞又提高了運行效率是复。蘋果從iPad2 開始使用雙核A5處理器(iPhone中從iPhone 4S開始使用)删顶,A7中還加入了協(xié)處理器,如何充分發(fā)揮這些處理器的性能確實值得思考淑廊。
簡介
當用戶播放音頻、下載資源特咆、進行圖像處理時往往希望做這些事情的時候其他操作不會被中斷或者希望這些操作過程中更加順暢季惩。在單線程中一個線程只能做一件事情,一件事情處理不完另一件事就不能開始腻格,這樣勢必影響用戶體驗画拾。早在單核處理器時期就有多線程,這個時候多線程更多的用于解決線程阻塞造成的用戶等待(通常是操作完UI后用戶不再干涉菜职,其他線程在等待隊列中青抛,CPU一旦空閑就繼續(xù)執(zhí)行,不影響用戶其他UI操作)酬核,其處理能力并沒有明顯的變化蜜另。如今無論是移動操作系統(tǒng)還是PC适室、服務(wù)器都是多核處理器,于是“并行運算”就更多的被提及举瑰。一件事情我們可以分成多個步驟捣辆,在沒有順序要求的情況下使用多線程既能解決線程阻塞又能充分利用多核處理器運行能力。
下圖反映了一個包含8個操作的任務(wù)在一個有兩核心的CPU中創(chuàng)建四個線程運行的情況此迅。假設(shè)每個核心有兩個線程汽畴,那么每個CPU中兩個線程會交替執(zhí)行,兩個CPU之間的操作會并行運算耸序。單就一個CPU而言兩個線程可以解決線程阻塞造成的不流暢問題忍些,其本身運行效率并沒有提高,多CPU的并行運算才真正解決了運行效率問題坎怪,這也正是并發(fā)和并行的區(qū)別罢坝。當然,不管是多核還是單核開發(fā)人員不用過多的擔心芋忿,因為任務(wù)具體分配給幾個CPU運算是由系統(tǒng)調(diào)度的炸客,開發(fā)人員不用過多關(guān)心系統(tǒng)有幾個CPU。開發(fā)人員需要關(guān)心的是線程之間的依賴關(guān)系戈钢,因為有些操作必須在某個操作完成完才能執(zhí)行痹仙,如果不能保證這個順序勢必會造成程序問題。
iOS多線程
在iOS中每個進程啟動后都會建立一個主線程(UI線程)殉了,這個線程是其他線程的父線程开仰。由于在iOS中除了主線程,其他子線程是獨立于Cocoa Touch的薪铜,所以只有主線程可以更新UI界面(新版iOS中众弓,使用其他線程更新UI可能也能成功,但是不推薦)隔箍。iOS中多線程使用并不復(fù)雜谓娃,關(guān)鍵是如何控制好各個線程的執(zhí)行順序、處理好資源競爭問題蜒滩。常用的多線程開發(fā)有三種方式:
- 1.NSThread
- 2.NSOperation
- 3.GCD
三種方式是隨著iOS的發(fā)展逐漸引入的滨达,所以相比而言后者比前者更加簡單易用,并且GCD也是目前蘋果官方比較推薦的方式(它充分利用了多核處理器的運算性能)俯艰。
NSThread
NSThread是輕量級的多線程開發(fā)捡遍,使用起來也并不復(fù)雜,但是使用NSThread需要自己管理線程生命周期竹握』辏可以使用對象方法
+(void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument
直接將操作添加到線程中并啟動,也可以使用對象方法
-(instancetype)initWithTarget:(id)target selector:(SEL)selector object:(id)argument
創(chuàng)建一個線程對象,然后調(diào)用start方法啟動線程谓传。
解決線程阻塞問題
在資源下載過程中蜈项,由于網(wǎng)絡(luò)原因有時候很難保證下載時間,如果不使用多線程可能用戶完成一個下載操作需要長時間的等待良拼,這個過程中無法進行其他操作战得。下面演示一個采用多線程下載圖片的過程,在這個示例中點擊按鈕會啟動一個線程去下載圖片庸推,下載完成后使用UIImageView將圖片顯示到界面中常侦。可以看到用戶點擊完下載按鈕后贬媒,不管圖片是否下載完成都可以繼續(xù)操作界面聋亡,不會造成阻塞。
//
// NSThread實現(xiàn)多線程
// MultiThread
//
// Created by Kenshin Cui on 14-3-22.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//
#import "KCMainViewController.h"
@interface KCMainViewController (){
UIImageView *_imageView;
}
@end
@implementation KCMainViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self layoutUI];
}
#pragma mark 界面布局
-(void)layoutUI{
_imageView =[[UIImageView alloc]initWithFrame:[UIScreen mainScreen].applicationFrame];
_imageView.contentMode=UIViewContentModeScaleAspectFit;
[self.view addSubview:_imageView];
UIButton *button=[UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame=CGRectMake(50, 500, 220, 25);
[button setTitle:@"加載圖片" forState:UIControlStateNormal];
//添加方法
[button addTarget:self action:@selector(loadImageWithMultiThread) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
}
#pragma mark 將圖片顯示到界面
-(void)updateImage:(NSData *)imageData{
UIImage *image=[UIImage imageWithData:imageData];
_imageView.image=image;
}
#pragma mark 請求圖片數(shù)據(jù)
-(NSData *)requestData{
//對于多線程操作建議把線程操作放到@autoreleasepool中
@autoreleasepool {
NSURL *url=[NSURL URLWithString:@"http://images.apple.com/iphone-6/overview/images/biggest_right_large.png"];
NSData *data=[NSData dataWithContentsOfURL:url];
return data;
}
}
#pragma mark 加載圖片
-(void)loadImage{
//請求數(shù)據(jù)
NSData *data= [self requestData];
/*將數(shù)據(jù)顯示到UI控件,注意只能在主線程中更新UI,
另外performSelectorOnMainThread方法是NSObject的分類方法际乘,每個NSObject對象都有此方法坡倔,
它調(diào)用的selector方法是當前調(diào)用控件的方法,例如使用UIImageView調(diào)用的時候selector就是UIImageView的方法
Object:代表調(diào)用方法的參數(shù),不過只能傳遞一個參數(shù)(如果有多個參數(shù)請使用對象進行封裝)
waitUntilDone:是否線程任務(wù)完成執(zhí)行
*/
[self performSelectorOnMainThread:@selector(updateImage:) withObject:data waitUntilDone:YES];
}
#pragma mark 多線程下載圖片
-(void)loadImageWithMultiThread{
//方法1:使用對象方法
//創(chuàng)建一個線程脖含,第一個參數(shù)是請求的操作罪塔,第二個參數(shù)是操作方法的參數(shù)
// NSThread *thread=[[NSThread alloc]initWithTarget:self selector:@selector(loadImage) object:nil];
// //啟動一個線程,注意啟動一個線程并非就一定立即執(zhí)行养葵,而是處于就緒狀態(tài)征堪,當系統(tǒng)調(diào)度時才真正執(zhí)行
// [thread start];
//方法2:使用類方法
[NSThread detachNewThreadSelector:@selector(loadImage) toTarget:self withObject:nil];
}
@end
運行效果:
程序比較簡單,但是需要注意執(zhí)行步驟:當點擊了“加載圖片”按鈕后啟動一個新的線程关拒,這個線程在演示中大概用了5s左右佃蚜,在這5s內(nèi)UI線程是不會阻塞的,用戶可以進行其他操作着绊,大約5s之后圖片下載完成谐算,此時調(diào)用UI線程將圖片顯示到界面中(這個過程瞬間完成)。另外前面也提到過归露,更新UI的時候使用UI線程洲脂,這里調(diào)用了NSObject的分類擴展方法,調(diào)用UI線程完成更新剧包。
擴展--NSObject分類擴展方法
為了簡化多線程開發(fā)過程腮考,蘋果官方對NSObject進行分類擴展(本質(zhì)還是創(chuàng)建NSThread),對于簡單的多線程操作可以直接使用這些擴展方法玄捕。
-(void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg:
在后臺執(zhí)行一個操作,本質(zhì)就是重新創(chuàng)建一個線程執(zhí)行當前方法棚放。
-(void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait
在指定的線程上執(zhí)行一個方法枚粘,需要用戶創(chuàng)建一個線程對象。
-(void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait
在主線程上執(zhí)行一個方法飘蚯。
NSOperation
使用NSOperation和NSOperationQueue進行多線程開發(fā)類似于C#中的線程池馍迄,只要將一個NSOperation(實際開中需要使用其子類NSInvocationOperation福也、NSBlockOperation)放到NSOperationQueue這個隊列中線程就會依次啟動。NSOperationQueue負責管理攀圈、執(zhí)行所有的NSOperation暴凑,在這個過程中可以更加容易的管理線程總數(shù)和控制線程之間的依賴關(guān)系。
NSOperation有兩個常用子類用于創(chuàng)建線程操作:NSInvocationOperation和NSBlockOperation赘来,兩種方式本質(zhì)沒有區(qū)別现喳,但是是后者使用Block形式進行代碼組織,使用相對方便犬辰。
NSInvocationOperation
首先使用NSInvocationOperation進行一張圖片的加載演示嗦篱,整個過程就是:創(chuàng)建一個操作,在這個操作中指定調(diào)用方法和參數(shù)幌缝,然后加入到操作隊列灸促。其他代碼基本不用修改,直接修加載圖片方法如下:
-(void)loadImageWithMultiThread{
/*創(chuàng)建一個調(diào)用操作
object:調(diào)用方法參數(shù)
*/
NSInvocationOperation *invocationOperation=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(loadImage) object:nil];
//創(chuàng)建完NSInvocationOperation對象并不會調(diào)用涵卵,它由一個start方法啟動操作浴栽,但是注意如果直接調(diào)用start方法,則此操作會在主線程中調(diào)用轿偎,一般不會這么操作,而是添加到NSOperationQueue中
// [invocationOperation start];
//創(chuàng)建操作隊列
NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
//注意添加到操作隊后典鸡,隊列會開啟一個線程執(zhí)行此操作
[operationQueue addOperation:invocationOperation];
}
NSBlockOperation
下面采用NSBlockOperation創(chuàng)建多個線程加載圖片。
#pragma mark 多線程下載圖片
-(void)loadImageWithMultiThread{
int count=ROW_COUNT*COLUMN_COUNT;
//創(chuàng)建操作隊列
NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
operationQueue.maxConcurrentOperationCount=5;//設(shè)置最大并發(fā)線程數(shù)
//創(chuàng)建多個線程用于填充圖片
for (int i=0; i<count; ++i){
//方法1:創(chuàng)建操作塊添加到隊列
// //創(chuàng)建多線程操作
// NSBlockOperation *blockOperation=[NSBlockOperation blockOperationWithBlock:^{
// [self loadImage:[NSNumber numberWithInt:i]];
// }];
// //創(chuàng)建操作隊列
//
// [operationQueue addOperation:blockOperation];
//方法2:直接使用操隊列添加操作
[operationQueue addOperationWithBlock:^{
[self loadImage:[NSNumber numberWithInt:i]];
}];
}
}
對比之前NSThread加載張圖片很發(fā)現(xiàn)核心代碼簡化了不少贴硫,這里著重強調(diào)兩點:
- 使用NSBlockOperation方法椿每,所有的操作不必單獨定義方法,同時解決了只能傳遞一個參數(shù)的問題英遭。
- 調(diào)用主線程隊列的addOperationWithBlock:方法進行UI更新间护,不用再定義一個參數(shù)實體(之前必須定義一個KCImageData解決只能傳遞一個參數(shù)的問題)。
- 使用NSOperation進行多線程開發(fā)可以設(shè)置最大并發(fā)線程挖诸,有效的對線程進行了控制(上面的代碼運行起來你會發(fā)現(xiàn)打印當前進程時只有有限的線程被創(chuàng)建汁尺,如上面的代碼設(shè)置最大線程數(shù)為5,則圖片基本上是五個一次加載的)多律。
線程執(zhí)行順序
前面使用NSThread很難控制線程的執(zhí)行順序痴突,但是使用NSOperation就容易多了,每個NSOperation可以設(shè)置依賴線程狼荞。假設(shè)操作A依賴于操作B辽装,線程操作隊列在啟動線程時就會首先執(zhí)行B操作,然后執(zhí)行A相味。對于前面優(yōu)先加載最后一張圖的需求拾积,只要設(shè)置前面的線程操作的依賴線程為最后一個操作即可。修改圖片加載方法如下:
-(void)loadImageWithMultiThread{
int count=ROW_COUNT*COLUMN_COUNT;
//創(chuàng)建操作隊列
NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
operationQueue.maxConcurrentOperationCount=5;//設(shè)置最大并發(fā)線程數(shù)
NSBlockOperation *lastBlockOperation=[NSBlockOperation blockOperationWithBlock:^{
[self loadImage:[NSNumber numberWithInt:(count-1)]];
}];
//創(chuàng)建多個線程用于填充圖片
for (int i=0; i<count-1; ++i) {
//方法1:創(chuàng)建操作塊添加到隊列
//創(chuàng)建多線程操作
NSBlockOperation *blockOperation=[NSBlockOperation blockOperationWithBlock:^{
[self loadImage:[NSNumber numberWithInt:i]];
}];
//設(shè)置依賴操作為最后一張圖片加載操作
[blockOperation addDependency:lastBlockOperation];
[operationQueue addOperation:blockOperation];
}
//將最后一個圖片的加載操作加入線程隊列
[operationQueue addOperation:lastBlockOperation];
}
GCD
GCD(Grand Central Dispatch)是基于C語言開發(fā)的一套多線程開發(fā)機制,也是目前蘋果官方推薦的多線程開發(fā)方法拓巧。前面也說過三種開發(fā)中GCD抽象層次最高斯碌,當然是用起來也最簡單,只是它基于C語言開發(fā)肛度,并不像NSOperation是面向?qū)ο蟮拈_發(fā)傻唾,而是完全面向過程的。對于熟悉C#異步調(diào)用的朋友對于GCD學(xué)習(xí)起來應(yīng)該很快承耿,因為它與C#中的異步調(diào)用基本是一樣的冠骄。這種機制相比較于前面兩種多線程開發(fā)方式最顯著的優(yōu)點就是它對于多核運算更加有效。
GCD中也有一個類似于NSOperationQueue的隊列瘩绒,GCD統(tǒng)一管理整個隊列中的任務(wù)猴抹。但是GCD中的隊列分為并行隊列和串行隊列兩類:
- 串行隊列:只有一個線程,加入到隊列中的操作按添加順序依次執(zhí)行锁荔。
- 并發(fā)隊列:有多個線程蟀给,操作進來之后它會將這些隊列安排在可用的處理器上,同時保證先進來的任務(wù)優(yōu)先處理阳堕。
其實在GCD中還有一個特殊隊列就是主隊列跋理,用來執(zhí)行主線程上的操作任務(wù)(從前面的演示中可以看到其實在NSOperation中也有一個主隊列)。
串行隊列
使用串行隊列時首先要創(chuàng)建一個串行隊列恬总,然后調(diào)用異步調(diào)用方法前普,在此方法中傳入串行隊列和線程操作即可自動執(zhí)行。下面使用線程隊列演示圖片的加載過程壹堰,你會發(fā)現(xiàn)多張圖片會按順序加載拭卿,因為當前隊列中只有一個線程。
#pragma mark 多線程下載圖片
-(void)loadImageWithMultiThread{
int count=ROW_COUNT*COLUMN_COUNT;
/*創(chuàng)建一個串行隊列
第一個參數(shù):隊列名稱
第二個參數(shù):隊列類型
*/
dispatch_queue_t serialQueue=dispatch_queue_create("myThreadQueue1", DISPATCH_QUEUE_SERIAL);//注意queue對象不是指針類型
//創(chuàng)建多個線程用于填充圖片
for (int i=0; i<count; ++i) {
//異步執(zhí)行隊列任務(wù)
dispatch_async(serialQueue, ^{
[self loadImage:[NSNumber numberWithInt:i]];
});
}
//非ARC環(huán)境請釋放
// dispatch_release(seriQueue);
}
在上面的代碼中更新UI還使用了GCD方法的主線程隊列dispatch_get_main_queue()贱纠,其實這與前面兩種主線程更新UI沒有本質(zhì)的區(qū)別峻厚。
并發(fā)隊列
并發(fā)隊列同樣是使用dispatch_queue_create()方法創(chuàng)建,只是最后一個參數(shù)指定為DISPATCH_QUEUE_CONCURRENT進行創(chuàng)建谆焊,但是在實際開發(fā)中我們通常不會重新創(chuàng)建一個并發(fā)隊列而是使用dispatch_get_global_queue()方法取得一個全局的并發(fā)隊列(當然如果有多個并發(fā)隊列可以使用前者創(chuàng)建)惠桃。下面通過并行隊列演示一下多個圖片的加載。代碼與上面串行隊列加載類似辖试,只需要修改照片加載方法如下:
-(void)loadImageWithMultiThread{
int count=ROW_COUNT*COLUMN_COUNT;
/*取得全局隊列
第一個參數(shù):線程優(yōu)先級
第二個參數(shù):標記參數(shù)辜王,目前沒有用,一般傳入0
*/
dispatch_queue_t globalQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//創(chuàng)建多個線程用于填充圖片
for (int i=0; i<count; ++i) {
//異步執(zhí)行隊列任務(wù)
dispatch_async(globalQueue, ^{
[self loadImage:[NSNumber numberWithInt:i]];
});
}
}
細心的朋友肯定會思考罐孝,既然可以使用dispatch_async()異步調(diào)用方法呐馆,是不是還有同步方法,確實如此莲兢,在GCD中還有一個dispatch_sync()方法摹恰。假設(shè)將上面的代碼修改為同步調(diào)用辫继,可以看到如下效果:
可以看點擊按鈕后按鈕無法再次點擊,因為所有圖片的加載全部在主線程中(可以打印線程查看)俗慈,主線程被阻塞,造成圖片最終是一次性顯示遣耍」脍澹可以得出結(jié)論:
- 在GDC中一個操作是多線程執(zhí)行還是單線程執(zhí)行取決于當前隊列類型和執(zhí)行方法,只有隊列類型為并行隊列并且使用異步方法執(zhí)行時才能在多個線程中執(zhí)行舵变。
- 串行隊列可以按順序執(zhí)行酣溃,并行隊列的異步方法無法確定執(zhí)行順序。
- UI界面的更新最好采用同步方法纪隙,其他操作采用異步方法赊豌。
- GCD中多線程操作方法不需要使用@autoreleasepool,GCD會管理內(nèi)存绵咱。
其他任務(wù)執(zhí)行方法
GCD執(zhí)行任務(wù)的方法并非只有簡單的同步調(diào)用方法和異步調(diào)用方法碘饼,還有其他一些常用方法:
- dispatch_apply():重復(fù)執(zhí)行某個任務(wù),但是注意這個方法沒有辦法異步執(zhí)行(為了不阻塞線程可以使用dispatch_async()包裝一下再執(zhí)行)悲伶。
- dispatch_once():單次執(zhí)行一個任務(wù)艾恼,此方法中的任務(wù)只會執(zhí)行一次,重復(fù)調(diào)用也沒辦法重復(fù)執(zhí)行(單例模式中常用此方法)麸锉。
- dispatch_time():延遲一定的時間后執(zhí)行钠绍。
- dispatch_barrier_async():使用此方法創(chuàng)建的任務(wù)首先會查看隊列中有沒有別的任務(wù)要執(zhí)行湃交,如果有溯祸,則會等待已有任務(wù)執(zhí)行完畢再執(zhí)行;同時在此方法后添加的任務(wù)必須等待此方法中任務(wù)執(zhí)行后才能執(zhí)行稠曼。(利用這個方法可以控制執(zhí)行順序碱屁,例如前面先加載最后一張圖片的需求就可以先使用這個方法將最后一張圖片加載的操作添加到隊列磷脯,然后調(diào)用dispatch_async()添加其他圖片加載任務(wù))
- dispatch_group_async():實現(xiàn)對任務(wù)分組管理,如果一組任務(wù)全部完成可以通過dispatch_group_notify()方法獲得完成通知(需要定義dispatch_group_t作為分組標識)忽媒。
總結(jié)
1.無論使用哪種方法進行多線程開發(fā)争拐,每個線程啟動后并不一定立即執(zhí)行相應(yīng)的操作,具體什么時候由系統(tǒng)調(diào)度(CPU空閑時就會執(zhí)行)晦雨。
2.更新UI應(yīng)該在主線程(UI線程)中進行架曹,并且推薦使用同步調(diào)用,常用的方法如下:
-(void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait
或者
-(void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL) wait
方法傳遞主線程,[NSThread mainThread]
[NSOperationQueue mainQueue] addOperationWithBlock:
使用dispatch_get_main_queue
dispatch_sync(dispatch_get_main_queue(), ^{})
3.NSThread適合輕量級多線程開發(fā)闹瞧,控制線程順序比較難绑雄,同時線程總數(shù)無法控制(每次創(chuàng)建并不能重用之前的線程,只能創(chuàng)建一個新的線程)奥邮。
4.對于簡單的多線程開發(fā)建議使用NSObject的擴展方法完成万牺,而不必使用NSThread罗珍。
5.可以使用NSThread的currentThread方法取得當前線程,使用 sleepForTimeInterval:方法讓當前線程休眠脚粟。
6.NSOperation進行多線程開發(fā)可以控制線程總數(shù)及線程依賴關(guān)系覆旱。
7.創(chuàng)建一個NSOperation不應(yīng)該直接調(diào)用start方法(如果直接start則會在主線程中調(diào)用)而是應(yīng)該放到NSOperationQueue中啟動。
8.相比NSInvocationOperation推薦使用NSBlockOperation核无,代碼簡單扣唱,同時由于閉包性使它沒有傳參問題。
9.NSOperation是對GCD面向?qū)ο蟮腛bjC封裝团南,但是相比GCD基于C語言開發(fā)噪沙,效率卻更高,建議如果任務(wù)之間有依賴關(guān)系或者想要監(jiān)聽任務(wù)完成狀態(tài)的情況下優(yōu)先選擇NSOperation否則使用GCD吐根。
10.在GCD中串行隊列中的任務(wù)被安排到一個單一線程執(zhí)行(不是主線程)正歼,可以方便地控制執(zhí)行順序;并發(fā)隊列在多個線程中執(zhí)行(前提是使用異步方法)拷橘,順序控制相對復(fù)雜局义,但是更高效。
11.在GDC中一個操作是多線程執(zhí)行還是單線程執(zhí)行取決于當前隊列類型和執(zhí)行方法膜楷,只有隊列類型為并行隊列并且使用異步方法執(zhí)行時才能在多個線程中執(zhí)行(如果是并行隊列使用同步方法調(diào)用則會在主線程中執(zhí)行)旭咽。