多線程開發(fā)
1.NSThread
2.NSOperation
3.GCD
三種方式是隨著iOS的發(fā)展逐漸引入的,所以相比而言后者比前者更加簡單易用,并且GCD也是目前蘋果官方比較推薦的方式(它充分利用了多核處理器的運算性能)。
在iOS中每個進程啟動后都會建立一個主線程(UI線程)雹食,這個線程是其他線程的父線程。由于在iOS中除了主線程颠猴,其他子線程是獨立于Cocoa Touch的棵帽,所以只有主線程可以更新UI界面.
NSThread
// 方法1. 對象方法 (手動開啟)
// (1)創(chuàng)建線程 通铲,把復雜任務放到子線程中執(zhí)行
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(blockThread:) object:nil];
// (2) 手動開啟
[thread start];
// 子線程
- (IBAction)blockThread:(id)sender {
@autoreleasepool {
// 凡是子線程執(zhí)行的方法,都添加到 @autoreleasepool{} 自動釋放池
}
}
// 方法2. 類方法 (無需手動開啟)
// 方法2. 創(chuàng)建線程片任,把復雜任務放到子線程中執(zhí)行
[NSThread detachNewThreadSelector:@selector(blockThread:) toTarget:self withObject:nil];
NSThread總結(jié):
1.每個線程的實際執(zhí)行順序并不一定按順序執(zhí)行(雖然是按順序啟動)偏友;
2.每個線程執(zhí)行時實際網(wǎng)絡狀況很可能不一致。當然網(wǎng)絡問題無法改變对供,只能盡可能讓網(wǎng)速更快位他,但是可以改變線程的優(yōu)先級,讓15個線程優(yōu)先執(zhí)行某個線程。線程優(yōu)先級范圍為0~1,值越大優(yōu)先級越高陷寝,每個線程的優(yōu)先級默認為0.5。
3.優(yōu)先級高的執(zhí)行窿冯,只是說,執(zhí)行的概率變高醋粟,并不是最先執(zhí)行靡菇。
4.使用NSThread在進行多線程開發(fā)過程中操作比較簡單重归,但是要控制線程執(zhí)行順序并不容易米愿,另外在這個過程中如果打印線程會發(fā)現(xiàn)循環(huán)幾次就創(chuàng)建了幾個線程,這在實際開發(fā)過程中是不得不考慮的問題鼻吮,因為每個線程的創(chuàng)建也是相當占用系統(tǒng)開銷的育苟。
線程狀態(tài)
線程狀態(tài)分為isExecuting(正在執(zhí)行)、isFinished(已經(jīng)完成)椎木、isCancellled(已經(jīng)取消)三種违柏。其中取消狀態(tài)程序可以干預設置,只要調(diào)用線程的cancel方法即可香椎。但是需要注意在主線程中僅僅能設置線程狀態(tài)漱竖,并不能真正停止當前線程,如果要終止線程必須在線程中調(diào)用exist方法畜伐,這是一個靜態(tài)方法馍惹,調(diào)用該方法可以退出當前線程。
在線程操作過程中可以讓某個線程休眠等待玛界,優(yōu)先執(zhí)行其他線程操作万矾,而且在這個過程中還可以修改某個線程的狀態(tài)或者終止某個指定線程。
-(NSData *)requestData:(int )index{
//對于多線程操作建議把線程操作放到@autoreleasepool中
@autoreleasepool{ //對一加載線程休眠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進行多線程開發(fā)類似于C#中的線程池慎框,只要將一個NSOperation(實際開中需要使用其子類NSInvocationOperation良狈、NSBlockOperation)放到NSOperationQueue這個隊列中線程就會依次啟動。NSOperationQueue負責管理笨枯、執(zhí)行所有的NSOperation薪丁,在這個過程中可以更加容易的管理線程總數(shù)和控制線程之間的依賴關系遇西。
NSOperation有兩個常用子類用于創(chuàng)建線程操作:NSInvocationOperation和NSBlockOperation,兩種方式本質(zhì)沒有區(qū)別窥突,但是是后者使用Block形式進行代碼組織努溃,使用相對方便。
注意:操作本身只是封裝了要執(zhí)行的相關方法阻问,并沒有開辟線程梧税,沒有主線程之分,在哪個線程中都能執(zhí)行称近。
// invocate 創(chuàng)建操作
// NSInvocationOperation *invo = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(blockThread:) object:nil];
// 開始任務
// [invo start];
// 1. 創(chuàng)建5個操作
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個block操作
NSBlockOperation *block1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"666666");
}];
NSBlockOperation *block2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"777777");
}];
// 3. 創(chuàng)建 操作隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 4. 設置最大并發(fā)數(shù)
queue.maxConcurrentOperationCount = 1; // 設置為 1第队,順序執(zhí)行
// 最大并發(fā)數(shù)控制的是同一時間點,能夠執(zhí)行的任務數(shù)刨秆。如果為1凳谦,則同時執(zhí)行1個,根據(jù)隊列FIFO特點衡未,一定是順序執(zhí)行尸执。 如果不為1,則可以同時執(zhí)行多個任務缓醋,同時執(zhí)行任務如失,稱為--并發(fā)執(zhí)行。
// 5. 添加操作--將操作添加到隊列
// [queue addOperation:invo];
[queue addOperation:invo1];
[queue addOperation:invo2];
[queue addOperation:invo3];
[queue addOperation:invo4];
[queue addOperation:invo5];
[queue addOperation:block1];
[queue addOperation:block2];
// 添加到隊列中的任務會自動執(zhí)行送粱,隊列內(nèi)部會開辟子線程褪贵,任務放在子線程中執(zhí)行。***********************************************
//方法2:直接使用操隊列添加操作,eg:block2
[queue addOperationWithBlock:^{
NSLog(@"777777");
}];
}
- 使用NSBlockOperation方法抗俄,所有的操作不必單獨定義方法脆丁,同時解決了只能傳遞一個參數(shù)的問題。
- 調(diào)用主線程隊列的 addOperationWithBlock: 方法進行UI更新动雹,不用再定義一個參數(shù)實體槽卫。
- 使用NSOperation進行多線程開發(fā)可以設置最大并發(fā)線程,有效的對線程進行了控制胰蝠。
線程執(zhí)行順序
使用NSThread很難控制線程的執(zhí)行順序歼培,但是使用NSOperation就容易多了,每個NSOperation可以設置依賴線程姊氓。假設操作A依賴于操作B丐怯,線程操作隊列在啟動線程時就會首先執(zhí)行B操作,然后執(zhí)行A翔横。
// 加載5張圖片读跷,優(yōu)先加載最后一張圖的需求,只要設置前面的線程操作的依賴線程為最后一個操作即可禾唁。
-(void)loadImageWithMultiThread{
int count=ROW_COUNT*COLUMN_COUNT; //創(chuàng)建操作隊列
NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
operationQueue.maxConcurrentOperationCount=5;
//設置最大并發(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]];
}];
//設置依賴操作為最后一張圖片加載操作
[blockOperation addDependency:lastBlockOperation];
[operationQueue addOperation:blockOperation];
}
//將最后一個圖片的加載操作加入線程隊列
[operationQueue addOperation:lastBlockOperation];
}
加載最后一張圖片的操作最后被加入到操作隊列效览,但是它卻是被第一個執(zhí)行的无切。操作依賴關系可以設置多個,例如A依賴于B丐枉、B依賴于C…但是千萬不要設置為循環(huán)依賴關系(例如A依賴于B哆键,B依賴于C,C又依賴于A)瘦锹,否則是不會被執(zhí)行的籍嘹。
GCD
- GCD(grand Center DisPath) 宏觀中心分配(隊列).
- 是蘋果開發(fā)的一種支持并行操作的機制。它的主要部件是一個FIFO隊列和一個線程池弯院,前者用來添加任務辱士,后者用來執(zhí)行任務。
- GCD中的FIFO隊列稱為dispatch queue听绳,它可以保證先進來的任務先得到執(zhí)行(但不保證一定先執(zhí)行結(jié)束)颂碘。
- GCD 是一個函數(shù)級的多線程,用C語言實現(xiàn)的椅挣。GCD 中可以分配多個隊列头岔,每個隊列都具有一定的功能。比如:串行隊列鼠证,并發(fā)隊列峡竣,分組隊列,只執(zhí)行一次隊列名惩。
dispatch queue分為下面兩種:
- Serial Dispatch Queue -- 線程池只提供一個線程用來執(zhí)行任務澎胡,所以后一個任務必須等到前一個任務執(zhí)行結(jié)束才能開始孕荠。
- Concurrent Dispatch Queue -- 線程池提供多個線程來執(zhí)行任務娩鹉,所以可以按序啟動多個任務并發(fā)執(zhí)行。
// 1. 創(chuàng)建一個串行隊列
dispatch_queue_t serialQ = dispatch_queue_create("q1", DISPATCH_QUEUE_SERIAL);
// 參數(shù)1. 隊列名稱
// 參數(shù)2. 隊列類型稚伍, 串行 還是 并行
// 2. 給隊列添加任務 -(以異步方式添加任務)
dispatch_async(serialQ, ^{
NSLog(@"%@",[NSThread currentThread]);
NSLog(@"建立網(wǎng)絡鏈接");
});
// 1. 創(chuàng)建一個并行隊列
dispatch_queue_t concurrentQ = dispatch_queue_create("eg.gcd.ConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
// 創(chuàng)建并行隊列的 全局隊列
dispatch_queue_t globleQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
// 參數(shù)1. 隊列的優(yōu)先級, 有4種弯予,默認default
// 參數(shù)2. 預留參數(shù),通常給 0.
// 注意: 不要使用優(yōu)先級使一個并行隊列个曙,變?yōu)橐粋€串行隊列锈嫩。優(yōu)先級高的執(zhí)行,只是說垦搬,執(zhí)行的概率變高呼寸,并不是最先執(zhí)行。
dispatch_async(concurrentQ, ^{
// Code here
});
// 釋放
dispatch_release(concurrentQ);
// 并行隊列的特點:雖然也遵守FIFO猴贰,但是提交時对雪,隊列中的任務并不會等待,如果前面的任務沒有執(zhí)行完米绕,不妨礙后面任務的執(zhí)行瑟捣。
// 并行隊列中會開辟多個子線程馋艺。
而系統(tǒng)默認就有一個串行隊列main_queue和并行隊列global_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的任務迈套,完成后在main_queue中更新UI捐祠,避免UI阻塞,無法響應用戶操作.
提交到隊列中的不同方式:
- (1) dispatch_once 這個函數(shù)桑李,它可以保證整個應用程序生命周期中某段代碼只被執(zhí)行一次.
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// code to be executed once
});
- (2) dispatch_after 延時執(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í)行某個代碼片段若干次。
dispatch_apply(10, globalQ, ^(size_t index) {
// 參數(shù)1. 重復提交的次數(shù)
// 參數(shù)2. 提交的隊列
// 參數(shù)3. 當前提交的次數(shù)
});
- (4) Dispatch Group 機制監(jiān)聽一組任務是否完成
// 1.創(chuàng)建隊列
dispatch_queue_t concurrentQ = dispatch_queue_create("q1", DISPATCH_QUEUE_CONCURRENT);
// 2. 創(chuàng)建一個組隊列
// 組隊列用途-- 把一系列的任務放到同一 組中贵白,再提交到隊列星岗。
dispatch_group_t group = dispatch_group_create();
// 3.添加任務 到組中
dispatch_group_async(group, concurrentQ, ^{
NSLog(@"上傳照片");
});
dispatch_group_async(group, concurrentQ, ^{
NSLog(@"獲取授權");
});
dispatch_group_async(group, concurrentQ, ^{
NSLog(@"保存信息");
});
// 需求:上面的三個操作可以并發(fā)執(zhí)行,最后提交信息戒洼,必須最后執(zhí)行俏橘。
// 組通知提交方式, 通知提交方式的任務圈浇,等到組中寥掐,若有任務執(zhí)行完畢后,才能執(zhí)行磷蜀。
dispatch_group_notify(group, concurrentQ, ^{
NSLog(@"最后提交信息");
});
- (5) dispatch_ barrier_ async 障礙提交
// 障礙提交方式一般用在召耘,并行隊列中,當障礙提交方式的任務執(zhí)行時褐隆,后面的任務等待污它。
// 障礙提交方式:只是當前執(zhí)行到此任務時,后面的任務等待庶弃。被障礙分割的上部分 和 下部分 執(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ù)
// 聲明一個函數(shù)
void string(void *s)
{
printf("%s\n",s);
}
- (IBAction)asyncf:(id)sender {
dispatch_async_f(dispatch_get_main_queue(), "aaaa", string);
// 參數(shù)1. 隊列
// 參數(shù)2. 傳遞給函數(shù)的參數(shù)
// 參數(shù)3. 函數(shù)名
}
- (7) dispatch_sync 同步提交
// 同步提交方式 --提交的block,如果沒有執(zhí)行完成屡穗,那么后面的所有代碼都不會執(zhí)行贴捡。
// 也就是說,提交操作村砂,在哪個線程中烂斋,就會阻塞哪個線程
// 注意:不管同步提交方式是提交到哪個線程,一定會阻塞當前線程,執(zhí)行也一定是在當前線程中源祈。
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);
}
});
線程互斥:
- 多個線程同時訪問同一個資源煎源,產(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
{
// 添加同步鎖,如果有一個線程正在訪問香缺,那么其他線程等待
NSLock *lock = [[NSLock alloc] init];
// 加鎖
[lock lock];
NSLog(@"剩余票數(shù)%d",--ticket);
[lock unlock]; // 解鎖
}