多線程種類
說句無關緊要的話,終于會用簡書的樣式引用了~~~本文借鑒大神的講解,鏈接在此:http://www.lxweimin.com/p/0b0d9b1f1f19
目前多線程分為四種:
Pthreads
NSThread
GCD
NSOperation&NSOperationQueue
Pthreads
一套在很多操作系統上都通用的多線程API,移植性很強,當然在 iOS 中也是可以的。是基于c語言的框架。
用法:
#import ?<pthread.h>
代碼用法:
pthread_t thread;
//創建一個線程并自動執行
pthread_create(&thread, NULL, start, NULL);
void *start(void *data) {
NSLog(@"%@", [NSThread currentThread]);
return NULL;
}
注:需要手動處理線程的各個狀態的轉換即管理生命周期,比如,這段代碼雖然創建了一個線程,但并沒有銷毀。
NSThread
經過蘋果封裝,但是仍然需要手動管理內存。
代碼用法:(手動啟動)
// 創建
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil];
// 啟動
[thread start];
(自動啟動)
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:nil];
除了創建啟動以外的其他方法:
//取消線程
- (void)cancel;
//啟動線程
- (void)start;
//判斷某個線程的狀態的屬性
@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
@property (readonly, getter=isCancelled) BOOL cancelled;
//設置和獲取線程名字
-(void)setName:(NSString *)name;
-(NSString *)name;
//獲取當前線程信息
+ (NSThread *)currentThread;
//獲取主線程信息
+ (NSThread *)mainThread;
//使當前線程暫停一段時間,或者暫停到某個時刻
+ (void)sleepForTimeInterval:(NSTimeInterval)time;
+ (void)sleepUntilDate:(NSDate *)date;
GCD
Grand Central Dispatch,它是蘋果為多核的并行運算提出的解決方案,所以會自動合理地利用更多的CPU內核(比如雙核、四核),最重要的是它會自動管理線程的生命周期(創建線程、調度任務、銷毀線程),完全不需要我們管理,我們只需要告訴干什么就行。同時它使用的也是c語言,不過由于使用了 Block(Swift里叫做閉包),使得使用起來更加方便,而且靈活。
任務和隊列
在GCD中,加入了兩個非常重要的概念:任務和隊列。
1)任務:即操作,你想要干什么,說白了就是一段代碼,在 GCD 中就是一個 Block,所以添加任務十分方便。任務有兩種執行方式:同步執行和異步執行,他們之間的區別是是否會創建新的線程。
同步執行:在當前的線程執行,不會開啟新線程,它會阻塞當前線程并等待Block中的任務執行完畢,然后當前線程才會繼續往下運行。
異步執行:會開啟新線程執行,不會阻塞當前線程。
2)隊列:用于存放任務。一共有兩種隊列,串行隊列和并行隊列。
串行隊列中的任務,GCD 會FIFO(先進先出)地取出來一個,執行一個,然后取下一個,這樣一個一個的執行。
并行隊列的任務,GCD 也會FIFO的取出來,但不同的是,它取出來一個就會放到別的線程,然后再取出來一個又放到另一個的線程。這樣由于取的動作很快,忽略不計,看起來,所有的任務都是一起執行的。不過需要注意,GCD 會根據系統資源控制并行的數量,所以如果任務很多,它并不會讓所有任務同時執行。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 同步執行 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?異步執行
串行隊列 ? ? ? ? ? ? 當前線程,一個一個執行 ? ? ? ? ? ? ? ? ? ? ? ? ?其他線程,一個一個執行
并行隊列 ? ? ? ? ? ? 當前線程,一個一個執行 ? ? ? ? ? ? ? ? ? ? ? ? ?其他線程,一起執行
3)創建隊列
>主隊列:這是一個特殊的串行隊列。它用于刷新 UI,任何需要刷新 UI 的工作都要在主隊列執行,所以一般耗時的任務都要放到別的線程執行。
dispatch_queue_t queue = ispatch_get_main_queue();
>自己創建的隊列:自己可以創建串行隊列, 也可以創建并行隊列。第二個參數用來表示創建的隊列是串行的還是并行的,傳入DISPATCH_QUEUE_SERIAL或NULL表示創建串行隊列。傳入DISPATCH_QUEUE_CONCURRENT表示創建并行隊列。
//串行隊列
dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", NULL);
dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_SERIAL);
//并行隊列
dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_CONCURRENT);
>全局并行隊列:只要是并行任務一般都加入到這個隊列。這是系統提供的一個并發隊列。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
4)創建任務
同步任務:會阻塞當前線程 (SYNC)
dispatch_sync(<#queue#>, ^{
//code here
NSLog(@"%@", [NSThread currentThread]);
});
異步任務:不會阻塞當前線程(ASYNC)
dispatch_async(<#queue#>, ^{
//code here
NSLog(@"%@", [NSThread currentThread]);
});
例子一:
NSLog("之前 - %@",NSThread.currentThread())
dispatch_sync(dispatch_get_main_queue(), { () ->Void in
NSLog("sync - %@",NSThread.currentThread())})
NSLog("之后 - %@",NSThread.currentThread())
解析:
同步任務會阻塞當前線程,然后把 Block 中的任務放到指定的隊列中執行,只有等到 Block 中的任務完成后才會讓當前線程繼續往下運行。
那么這里的步驟就是:打印完第一句后,dispatch_sync立即阻塞當前的主線程,然后把 Block 中的任務放到main_queue中,可是main_queue中的任務會被取出來放到主線程中執行,但主線程這個時候已經被阻塞了,所以 Block 中的任務就不能完成,它不完成,dispatch_sync就會一直阻塞主線程,這就是死鎖現象。導致主線程一直卡死。
例子二:
let queue = dispatch_queue_create("myQueue",DISPATCH_QUEUE_SERIAL)
NSLog("之前 - %@",NSThread.currentThread())? ??
dispatch_async(queue, { () ->Void in
NSLog("sync之前 - %@",NSThread.currentThread())? ? ? ??
dispatch_sync(queue, { () ->Void in
NSLog("sync - %@",NSThread.currentThread())? ? ? ? })
NSLog("sync之后 - %@",NSThread.currentThread())? })
NSLog("之后 - %@",NSThread.currentThread())
解析:
使用DISPATCH_QUEUE_SERIAL這個參數,創建了一個串行隊列。
打印出之前 - %@這句。
dispatch_async異步執行,所以當前線程不會被阻塞,于是有了兩條線程,一條當前線程繼續往下打印出之后 - %@這句, 另一臺執行 Block 中的內容打印sync之前 - %@這句。因為這兩條是并行的,所以打印的先后順序無所謂。
注意,高潮來了。現在的情況和上一個例子一樣了。dispatch_sync同步執行,于是它所在的線程會被阻塞,一直等到sync里的任務執行完才會繼續往下。于是sync就高興的把自己 Block 中的任務放到queue中,可誰想queue是一個串行隊列,一次執行一個任務,所以sync的 Block 必須等到前一個任務執行完畢,可萬萬沒想到的是queue正在執行的任務就是被sync阻塞了的那個。于是又發生了死鎖。所以sync所在的線程被卡死了。剩下的兩句代碼自然不會打印。
隊列組
隊列組可以將很多隊列添加到一個組里,這樣做的好處是,當這個組里所有的任務都執行完了,隊列組會通過一個方法通知我們。
//1.創建隊列組
dispatch_group_t group = dispatch_group_create();
//2.創建隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//3.多次使用隊列組的方法執行任務, 只有異步方法
//3.1.執行3次循環
dispatch_group_async(group, queue, ^{
for (NSInteger i = 0; i < 3; i++) {
NSLog(@"group-01 - %@", [NSThread currentThread]);
}
});
//3.2.主隊列執行8次循環
dispatch_group_async(group, dispatch_get_main_queue(), ^{
for (NSInteger i = 0; i < 8; i++) {
NSLog(@"group-02 - %@", [NSThread currentThread]);
}
});
//3.3.執行5次循環
dispatch_group_async(group, queue, ^{
for (NSInteger i = 0; i < 5; i++) {
NSLog(@"group-03 - %@", [NSThread currentThread]);
}
});
//4.都完成后會自動通知
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"完成 - %@", [NSThread currentThread]);
});
NSOperation&NSOperationQueue
NSOperation 是蘋果公司對 GCD 的封裝,完全面向對象,所以使用起來更好理解。 大家可以看到NSOperation 和 NSOperationQueue分別對應 GCD 的任務 和 隊列。操作步驟也很好理解:
將要執行的任務封裝到一個NSOperation對象中。
將此任務添加到一個NSOperationQueue對象中。
1)添加任務
NSOperation只是一個抽象類,所以不能封裝任務。但它有 2 個子類用于封裝任務。分別是:NSInvocationOperation和NSBlockOperation。創建一個 Operation 后,需要調用start方法來啟動任務,它會默認在當前隊列同步執行。當然你也可以在中途取消一個任務,只需要調用其cancel方法即可。
>NSInvocationOperation : 需要傳入一個方法名。
//1.創建NSInvocationOperation對象
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
//2.開始執行
[operation start];
>NSBlockOperation:
//1.創建NSBlockOperation對象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
//2.開始任務
[operation start];
NSBlockOperation還有一個方法:addExecutionBlock:,通過這個方法可以給 Operation 添加多個執行 Block。這樣 Operation 中的任務會并發執行,它會在主線程和其它的多個線程執行這些任務。
//1.創建NSBlockOperation對象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
//添加多個Block
for (NSInteger i = 0; i < 5; i++) {
[operation addExecutionBlock:^{
NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
}];
}
//2.開始任務
[operation start];
注:addExecutionBlock方法必須在start()方法之前執行!
2)創建隊列
>主隊列
NSOperationQueue *queue = [NSOperationQueue mainQueue];
>其他隊列
因為主隊列比較特殊,所以會單獨有一個類方法來獲得主隊列。那么通過初始化產生的隊列就是其他隊列。
//1.創建一個其他隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//2.創建NSBlockOperation對象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
//3.添加多個Block
for (NSInteger i = 0; i < 5; i++) {
[operation addExecutionBlock:^{
NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
}];
}
//4.隊列添加任務
[queue addOperation:operation];
>隊列添加依賴
//1.任務一:下載圖片
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下載圖片 - %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
}];
//2.任務二:打水印
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"打水印? - %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
}];
//3.任務三:上傳圖片
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"上傳圖片 - %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
}];
//4.設置依賴
[operation2 addDependency:operation1];? ? ? //任務二依賴任務一
[operation3 addDependency:operation2];? ? ? //任務三依賴任務二
//5.創建隊列并加入任務
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperations:@[operation3, operation2, operation1] waitUntilFinished:NO];
注:不能添加相互依賴,會死鎖,比如 A依賴B,B依賴A。
可以使用removeDependency來解除依賴關系。
可以在不同的隊列之間依賴,反正就是這個依賴是添加到任務身上的,和隊列沒關系。