iOS -- 多線程開發(fā)

多線程開發(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分為下面兩種:

    1. Serial Dispatch Queue -- 線程池只提供一個(gè)線程用來執(zhí)行任務(wù),所以后一個(gè)任務(wù)必須等到前一個(gè)任務(wù)執(zhí)行結(jié)束才能開始。
    1. 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]; // 解鎖
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 概覽 大家都知道,在開發(fā)過程中應(yīng)該盡可能減少用戶等待時(shí)間,讓程序盡可能快的完成運(yùn)算??墒菬o論是哪種語言開發(fā)的程序最...
    周末年安閱讀 1,955評(píng)論 1 50
  • NSThread 第一種:通過NSThread的對(duì)象方法 NSThread *thread = [[NSThrea...
    攻城獅GG閱讀 947評(píng)論 0 3
  • 多線程概念 線程線程指的是:1個(gè)CPU執(zhí)行的CPU命令列為一條無分叉路徑 多線程這種無分叉路徑不止一條,存在多條即...
    我系哆啦閱讀 656評(píng)論 0 5
  • 一、NSThread 1、創(chuàng)建和啟動(dòng)線程 2、其他創(chuàng)建線程方式 上述2種創(chuàng)建線程方式的優(yōu)缺點(diǎn)優(yōu)點(diǎn):簡單快捷缺點(diǎn):無...
    小輝輝___閱讀 692評(píng)論 0 18
  • 點(diǎn)斑已經(jīng)整兩周了。 現(xiàn)在臉上還有十來個(gè)痂沒掉。掉痂后的皮膚呈有些凹陷的黑紅色,也不好看,但是比起曾經(jīng)滿臉的黑圈,我...
    一念見花開閱讀 162評(píng)論 0 0

友情鏈接更多精彩內(nèi)容