多線程開發(fā)
1.NSThread
2.NSOperation
3.GCD
三種方式是隨著iOS的發(fā)展逐漸引入的,所以相比而言后者比前者更加簡單易用,并且GCD也是目前蘋果官方比較推薦的方式(它充分利用了多核處理器的運(yùn)算性能)。
在iOS中每個(gè)進(jìn)程啟動(dòng)后都會(huì)建立一個(gè)主線程(UI線程),這個(gè)線程是其他線程的父線程。由于在iOS中除了主線程,其他子線程是獨(dú)立于Cocoa Touch的,所以只有主線程可以更新UI界面.
NSThread
// 方法1. 對(duì)象方法 (手動(dòng)開啟)
// (1)創(chuàng)建線程 ,把復(fù)雜任務(wù)放到子線程中執(zhí)行
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(blockThread:) object:nil];
// (2) 手動(dòng)開啟
[thread start];
// 子線程
- (IBAction)blockThread:(id)sender {
@autoreleasepool {
// 凡是子線程執(zhí)行的方法,都添加到 @autoreleasepool{} 自動(dòng)釋放池
}
}
// 方法2. 類方法 (無需手動(dòng)開啟)
// 方法2. 創(chuàng)建線程,把復(fù)雜任務(wù)放到子線程中執(zhí)行
[NSThread detachNewThreadSelector:@selector(blockThread:) toTarget:self withObject:nil];
NSThread總結(jié):
1.每個(gè)線程的實(shí)際執(zhí)行順序并不一定按順序執(zhí)行(雖然是按順序啟動(dòng));
2.每個(gè)線程執(zhí)行時(shí)實(shí)際網(wǎng)絡(luò)狀況很可能不一致。當(dāng)然網(wǎng)絡(luò)問題無法改變,只能盡可能讓網(wǎng)速更快,但是可以改變線程的優(yōu)先級(jí),讓15個(gè)線程優(yōu)先執(zhí)行某個(gè)線程。線程優(yōu)先級(jí)范圍為0~1,值越大優(yōu)先級(jí)越高,每個(gè)線程的優(yōu)先級(jí)默認(rèn)為0.5。
3.優(yōu)先級(jí)高的執(zhí)行,只是說,執(zhí)行的概率變高,并不是最先執(zhí)行。
4.使用NSThread在進(jìn)行多線程開發(fā)過程中操作比較簡單,但是要控制線程執(zhí)行順序并不容易,另外在這個(gè)過程中如果打印線程會(huì)發(fā)現(xiàn)循環(huán)幾次就創(chuàng)建了幾個(gè)線程,這在實(shí)際開發(fā)過程中是不得不考慮的問題,因?yàn)槊總€(gè)線程的創(chuàng)建也是相當(dāng)占用系統(tǒng)開銷的。
線程狀態(tài)
線程狀態(tài)分為isExecuting(正在執(zhí)行)、isFinished(已經(jīng)完成)、isCancellled(已經(jīng)取消)三種。其中取消狀態(tài)程序可以干預(yù)設(shè)置,只要調(diào)用線程的cancel方法即可。但是需要注意在主線程中僅僅能設(shè)置線程狀態(tài),并不能真正停止當(dāng)前線程,如果要終止線程必須在線程中調(diào)用exist方法,這是一個(gè)靜態(tài)方法,調(diào)用該方法可以退出當(dāng)前線程。
在線程操作過程中可以讓某個(gè)線程休眠等待,優(yōu)先執(zhí)行其他線程操作,而且在這個(gè)過程中還可以修改某個(gè)線程的狀態(tài)或者終止某個(gè)指定線程。
-(NSData *)requestData:(int )index{
//對(duì)于多線程操作建議把線程操作放到@autoreleasepool中
@autoreleasepool{ //對(duì)一加載線程休眠2秒
if (index!=(ROW_COUNT*COLUMN_COUNT-1)) {
[NSThread sleepForTimeInterval:2.0];
}
NSURL *url=[NSURL URLWithString:_imageNames[index]];
NSData *data=[NSData dataWithContentsOfURL:url]; return data;
}
}
NSOperation
使用NSOperation和NSOperationQueue進(jìn)行多線程開發(fā)類似于C#中的線程池,只要將一個(gè)NSOperation(實(shí)際開中需要使用其子類NSInvocationOperation、NSBlockOperation)放到NSOperationQueue這個(gè)隊(duì)列中線程就會(huì)依次啟動(dòng)。NSOperationQueue負(fù)責(zé)管理、執(zhí)行所有的NSOperation,在這個(gè)過程中可以更加容易的管理線程總數(shù)和控制線程之間的依賴關(guān)系。
NSOperation有兩個(gè)常用子類用于創(chuàng)建線程操作:NSInvocationOperation和NSBlockOperation,兩種方式本質(zhì)沒有區(qū)別,但是是后者使用Block形式進(jìn)行代碼組織,使用相對(duì)方便。
注意:操作本身只是封裝了要執(zhí)行的相關(guān)方法,并沒有開辟線程,沒有主線程之分,在哪個(gè)線程中都能執(zhí)行。
// invocate 創(chuàng)建操作
// NSInvocationOperation *invo = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(blockThread:) object:nil];
// 開始任務(wù)
// [invo start];
// 1. 創(chuàng)建5個(gè)操作
NSInvocationOperation *invo1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(printString:) object:@"11"];
NSInvocationOperation *invo2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(printString:) object:@"22"];
NSInvocationOperation *invo3 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(printString:) object:@"33"];
NSInvocationOperation *invo4 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(printString:) object:@"44"];
NSInvocationOperation *invo5 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(printString:) object:@"55"];
// 2. NSBlockOperation 創(chuàng)建2個(gè)block操作
NSBlockOperation *block1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"666666");
}];
NSBlockOperation *block2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"777777");
}];
// 3. 創(chuàng)建 操作隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 4. 設(shè)置最大并發(fā)數(shù)
queue.maxConcurrentOperationCount = 1; // 設(shè)置為 1,順序執(zhí)行
// 最大并發(fā)數(shù)控制的是同一時(shí)間點(diǎn),能夠執(zhí)行的任務(wù)數(shù)。如果為1,則同時(shí)執(zhí)行1個(gè),根據(jù)隊(duì)列FIFO特點(diǎn),一定是順序執(zhí)行。 如果不為1,則可以同時(shí)執(zhí)行多個(gè)任務(wù),同時(shí)執(zhí)行任務(wù),稱為--并發(fā)執(zhí)行。
// 5. 添加操作--將操作添加到隊(duì)列
// [queue addOperation:invo];
[queue addOperation:invo1];
[queue addOperation:invo2];
[queue addOperation:invo3];
[queue addOperation:invo4];
[queue addOperation:invo5];
[queue addOperation:block1];
[queue addOperation:block2];
// 添加到隊(duì)列中的任務(wù)會(huì)自動(dòng)執(zhí)行,隊(duì)列內(nèi)部會(huì)開辟子線程,任務(wù)放在子線程中執(zhí)行。***********************************************
//方法2:直接使用操隊(duì)列添加操作,eg:block2
[queue addOperationWithBlock:^{
NSLog(@"777777");
}];
}
- 使用NSBlockOperation方法,所有的操作不必單獨(dú)定義方法,同時(shí)解決了只能傳遞一個(gè)參數(shù)的問題。
- 調(diào)用主線程隊(duì)列的 addOperationWithBlock: 方法進(jìn)行UI更新,不用再定義一個(gè)參數(shù)實(shí)體。
- 使用NSOperation進(jìn)行多線程開發(fā)可以設(shè)置最大并發(fā)線程,有效的對(duì)線程進(jìn)行了控制。
線程執(zhí)行順序
使用NSThread很難控制線程的執(zhí)行順序,但是使用NSOperation就容易多了,每個(gè)NSOperation可以設(shè)置依賴線程。假設(shè)操作A依賴于操作B,線程操作隊(duì)列在啟動(dòng)線程時(shí)就會(huì)首先執(zhí)行B操作,然后執(zhí)行A。
// 加載5張圖片,優(yōu)先加載最后一張圖的需求,只要設(shè)置前面的線程操作的依賴線程為最后一個(gè)操作即可。
-(void)loadImageWithMultiThread{
int count=ROW_COUNT*COLUMN_COUNT; //創(chuàng)建操作隊(duì)列
NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
operationQueue.maxConcurrentOperationCount=5;
//設(shè)置最大并發(fā)線程數(shù)
NSBlockOperation *lastBlockOperation=[NSBlockOperation blockOperationWithBlock:^{
[self loadImage:[NSNumber numberWithInt:(count-1)]];
}];
//創(chuàng)建多個(gè)線程用于填充圖片
for (int i=0; i<count-1; ++i) {
//方法1:創(chuàng)建操作塊添加到隊(duì)列
//創(chuàng)建多線程操作
NSBlockOperation *blockOperation=[NSBlockOperation blockOperationWithBlock:^{
[self loadImage:[NSNumber numberWithInt:i]];
}];
//設(shè)置依賴操作為最后一張圖片加載操作
[blockOperation addDependency:lastBlockOperation];
[operationQueue addOperation:blockOperation];
}
//將最后一個(gè)圖片的加載操作加入線程隊(duì)列
[operationQueue addOperation:lastBlockOperation];
}
加載最后一張圖片的操作最后被加入到操作隊(duì)列,但是它卻是被第一個(gè)執(zhí)行的。操作依賴關(guān)系可以設(shè)置多個(gè),例如A依賴于B、B依賴于C…但是千萬不要設(shè)置為循環(huán)依賴關(guān)系(例如A依賴于B,B依賴于C,C又依賴于A),否則是不會(huì)被執(zhí)行的。
GCD
- GCD(grand Center DisPath) 宏觀中心分配(隊(duì)列).
- 是蘋果開發(fā)的一種支持并行操作的機(jī)制。它的主要部件是一個(gè)FIFO隊(duì)列和一個(gè)線程池,前者用來添加任務(wù),后者用來執(zhí)行任務(wù)。
- GCD中的FIFO隊(duì)列稱為dispatch queue,它可以保證先進(jìn)來的任務(wù)先得到執(zhí)行(但不保證一定先執(zhí)行結(jié)束)。
- GCD 是一個(gè)函數(shù)級(jí)的多線程,用C語言實(shí)現(xiàn)的。GCD 中可以分配多個(gè)隊(duì)列,每個(gè)隊(duì)列都具有一定的功能。比如:串行隊(duì)列,并發(fā)隊(duì)列,分組隊(duì)列,只執(zhí)行一次隊(duì)列。
dispatch queue分為下面兩種:
- Serial Dispatch Queue -- 線程池只提供一個(gè)線程用來執(zhí)行任務(wù),所以后一個(gè)任務(wù)必須等到前一個(gè)任務(wù)執(zhí)行結(jié)束才能開始。
- Concurrent Dispatch Queue -- 線程池提供多個(gè)線程來執(zhí)行任務(wù),所以可以按序啟動(dòng)多個(gè)任務(wù)并發(fā)執(zhí)行。
// 1. 創(chuàng)建一個(gè)串行隊(duì)列
dispatch_queue_t serialQ = dispatch_queue_create("q1", DISPATCH_QUEUE_SERIAL);
// 參數(shù)1. 隊(duì)列名稱
// 參數(shù)2. 隊(duì)列類型, 串行 還是 并行
// 2. 給隊(duì)列添加任務(wù) -(以異步方式添加任務(wù))
dispatch_async(serialQ, ^{
NSLog(@"%@",[NSThread currentThread]);
NSLog(@"建立網(wǎng)絡(luò)鏈接");
});
// 1. 創(chuàng)建一個(gè)并行隊(duì)列
dispatch_queue_t concurrentQ = dispatch_queue_create("eg.gcd.ConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
// 創(chuàng)建并行隊(duì)列的 全局隊(duì)列
dispatch_queue_t globleQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
// 參數(shù)1. 隊(duì)列的優(yōu)先級(jí), 有4種,默認(rèn)default
// 參數(shù)2. 預(yù)留參數(shù),通常給 0.
// 注意: 不要使用優(yōu)先級(jí)使一個(gè)并行隊(duì)列,變?yōu)橐粋€(gè)串行隊(duì)列。優(yōu)先級(jí)高的執(zhí)行,只是說,執(zhí)行的概率變高,并不是最先執(zhí)行。
dispatch_async(concurrentQ, ^{
// Code here
});
// 釋放
dispatch_release(concurrentQ);
// 并行隊(duì)列的特點(diǎn):雖然也遵守FIFO,但是提交時(shí),隊(duì)列中的任務(wù)并不會(huì)等待,如果前面的任務(wù)沒有執(zhí)行完,不妨礙后面任務(wù)的執(zhí)行。
// 并行隊(duì)列中會(huì)開辟多個(gè)子線程。
而系統(tǒng)默認(rèn)就有一個(gè)串行隊(duì)列main_queue和并行隊(duì)列g(shù)lobal_queue:
dispatch_queue_t globalQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t mainQ = dispatch_get_main_queue();
通常,我們可以在global_queue中做一些long-running的任務(wù),完成后在main_queue中更新UI,避免UI阻塞,無法響應(yīng)用戶操作.
提交到隊(duì)列中的不同方式:
- (1) dispatch_once 這個(gè)函數(shù),它可以保證整個(gè)應(yīng)用程序生命周期中某段代碼只被執(zhí)行一次.
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// code to be executed once
});
- (2) dispatch_after 延時(shí)執(zhí)行
// 延遲3s 后,改變背景顏色
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.view.backgroundColor = [UIColor redColor];
});
- (3) dispatch_apply 執(zhí)行某個(gè)代碼片段若干次。
dispatch_apply(10, globalQ, ^(size_t index) {
// 參數(shù)1. 重復(fù)提交的次數(shù)
// 參數(shù)2. 提交的隊(duì)列
// 參數(shù)3. 當(dāng)前提交的次數(shù)
});
- (4) Dispatch Group 機(jī)制監(jiān)聽一組任務(wù)是否完成
// 1.創(chuàng)建隊(duì)列
dispatch_queue_t concurrentQ = dispatch_queue_create("q1", DISPATCH_QUEUE_CONCURRENT);
// 2. 創(chuàng)建一個(gè)組隊(duì)列
// 組隊(duì)列用途-- 把一系列的任務(wù)放到同一 組中,再提交到隊(duì)列。
dispatch_group_t group = dispatch_group_create();
// 3.添加任務(wù) 到組中
dispatch_group_async(group, concurrentQ, ^{
NSLog(@"上傳照片");
});
dispatch_group_async(group, concurrentQ, ^{
NSLog(@"獲取授權(quán)");
});
dispatch_group_async(group, concurrentQ, ^{
NSLog(@"保存信息");
});
// 需求:上面的三個(gè)操作可以并發(fā)執(zhí)行,最后提交信息,必須最后執(zhí)行。
// 組通知提交方式, 通知提交方式的任務(wù),等到組中,若有任務(wù)執(zhí)行完畢后,才能執(zhí)行。
dispatch_group_notify(group, concurrentQ, ^{
NSLog(@"最后提交信息");
});
- (5) dispatch_ barrier_ async 障礙提交
// 障礙提交方式一般用在,并行隊(duì)列中,當(dāng)障礙提交方式的任務(wù)執(zhí)行時(shí),后面的任務(wù)等待。
// 障礙提交方式:只是當(dāng)前執(zhí)行到此任務(wù)時(shí),后面的任務(wù)等待。被障礙分割的上部分 和 下部分 執(zhí)行順序 不確定。 可能是上部分先執(zhí)行,也可能是下部分先執(zhí)行。
dispatch_async(concurrentQ, blk0);
dispatch_async(concurrentQ, blk1);
// 添加障礙,執(zhí)行寫入操作,寫入沒有執(zhí)行完之前,不允許讀取數(shù)據(jù)。
dispatch_barrier_async(concurrentQ, blk_barrier);
dispatch_async(concurrentQ, blk2);
- (6) dispatch_ async_ f 提交函數(shù)
// 聲明一個(gè)函數(shù)
void string(void *s)
{
printf("%s\n",s);
}
- (IBAction)asyncf:(id)sender {
dispatch_async_f(dispatch_get_main_queue(), "aaaa", string);
// 參數(shù)1. 隊(duì)列
// 參數(shù)2. 傳遞給函數(shù)的參數(shù)
// 參數(shù)3. 函數(shù)名
}
- (7) dispatch_sync 同步提交
// 同步提交方式 --提交的block,如果沒有執(zhí)行完成,那么后面的所有代碼都不會(huì)執(zhí)行。
// 也就是說,提交操作,在哪個(gè)線程中,就會(huì)阻塞哪個(gè)線程
// 注意:不管同步提交方式是提交到哪個(gè)線程,一定會(huì)阻塞當(dāng)前線程,執(zhí)行也一定是在當(dāng)前線程中。
dispatch_queue_t conQ = dispatch_queue_create("q1", DISPATCH_QUEUE_CONCURRENT);
// 2. 同步提交 --
dispatch_sync(conQ, ^{
for (int i = 0; i < 65000; i++) {
NSLog(@"%@",[NSThread currentThread]);
NSLog(@"%d",i);
}
});
線程互斥:
- 多個(gè)線程同時(shí)訪問同一個(gè)資源,產(chǎn)生的資源爭奪問題.
static int ticket = 10;
- (IBAction)threadConflict:(id)sender {
dispatch_queue_t concurrentQ = dispatch_queue_create("q1", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(10, concurrentQ, ^(size_t i) {
[self sellTicket];
});
}
-(void)sellTicket
{
// 添加同步鎖,如果有一個(gè)線程正在訪問,那么其他線程等待
NSLock *lock = [[NSLock alloc] init];
// 加鎖
[lock lock];
NSLog(@"剩余票數(shù)%d",--ticket);
[lock unlock]; // 解鎖
}