筆記-多線程

2017/02/20
今天的目標(biāo)只有一個:弄懂多線程(真心的)

一、認(rèn)識多線程

進(jìn)程:

在系統(tǒng)中正在運行的一個應(yīng)用程序;

線程:

一個進(jìn)程想要執(zhí)行任務(wù),必須得有線程(每一個進(jìn)程至少有一條線程),線程是進(jìn)程的基本執(zhí)行單元,一個進(jìn)程的所有任務(wù)都在線程中執(zhí)行。

多線程:

一個進(jìn)程中可以開啟多條線程,每條線程可以并行執(zhí)行不同的任務(wù)(比如:進(jìn)程->車間,線程->車間工人)
(1)每一個程序都有一個主線程,程序啟動時創(chuàng)建(調(diào)用main來啟動)
(2)主線程的生命周期是和應(yīng)用程序綁定的,程序退出(結(jié)束)時,主線程也就停止了
(3)多線程技術(shù)表示,一個應(yīng)用程序有多個線程,使用多線程能提供CPU的使用率,防止主線程的堵塞
(4)任何有可能堵塞主線程的任務(wù)不要在主線程執(zhí)行(訪問網(wǎng)絡(luò))

二、多線程的原理

同一時間,CPU只能處理1條線程,只有1條線程在工作(執(zhí)行)
多線程并發(fā)(同時)執(zhí)行,其實是CPU快速地在多條線程之間調(diào)度(切換)
如果CPU調(diào)度線程的時間足夠快,就造成了多線程并發(fā)執(zhí)行的假象

思考:如果線程非常非常多,會發(fā)生什么情況?

CPU會在N多線程之間調(diào)度,CPU會累死,消耗大量的CPU資源
每條線程被調(diào)度執(zhí)行的頻次會降低(線程的執(zhí)行效率降低)

三、多線程的優(yōu)缺點

優(yōu)點:
  • 能適當(dāng)提高程序的執(zhí)行效率
  • 能適當(dāng)提高資源的利用率(CPU、內(nèi)存的利用率)
缺點:
  • 開啟線程需要占用一定的內(nèi)存空間(默認(rèn)情況下,主線程占用了1M,子線程占用512K),如果開啟大量的線程,會占用大量的內(nèi)存空間,降低程序的性能
  • 線程越多,CPU在調(diào)度線程上的開銷就越大
  • 程序設(shè)計更加復(fù)雜:比如線程之間的通信、多線程的數(shù)據(jù)共享

四、多線程在iOS開發(fā)中的應(yīng)用

主線程:一個iOS程序運行后,默認(rèn)會開啟一條線程,稱為“主線程”或“UI線程”
主線程的主要作用:

  • 顯示/刷新UI界面
  • 處理UI事件(比如:點擊事件、滾動事件、拖拽事件等)

主線程的使用注意:別將比較耗時操作放到主線程中。
耗時操作會卡住主線程,嚴(yán)重影響UI的流暢度,給用戶一種“卡”的壞體驗

使用場合:
  • 耗時操作,例如:網(wǎng)絡(luò)圖片、視頻、歌曲、書籍等資源下載
  • 游戲中的聲音播放

五、iOS的三種多線程技術(shù)

1、NSThread:

優(yōu)點:NSThread比其他兩個輕量級,使用簡單
缺點:不能控制線程的執(zhí)行順序(需要自己管理線程的生命周期、線程同步、加鎖、睡眠以及喚醒等。線程同步對數(shù)據(jù)的加鎖對會有一定的系統(tǒng)開銷)【需要使用start方法,才能啟動實例化出來的線程、控制并發(fā)線程數(shù)、先后順序困難,例如:下載圖片(后臺線程)->濾鏡美化(后臺線程)->更新UI(主線程)】

  @implementation ViewController

  - (void)viewDidLoad {
      [super viewDidLoad];

      for(int i=1; i<100; i++) {
         NSLog(@"=====%@======%d",[[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil].name,i);
         if (i == 7) {
         //創(chuàng)建線程對象
         NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
         //啟動新線程
         [thread start];
        /**
         *  創(chuàng)建并啟動新線程
         */
        //[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
        /**
         * 上面兩種方法本質(zhì)上都是將target對象的selector方法轉(zhuǎn)換為線程執(zhí)行體,其中selector方法最多可以接收一個參數(shù),而argument就是代表傳給selector方法的參數(shù)。這兩種創(chuàng)建新線程的方式并沒有明顯的區(qū)別,只是上面的是一個實例化方法,該方法返回一個thread對象,調(diào)用start方法開啟線程。下面的不會返回NSThread對象,因此這種方法會直接創(chuàng)建并啟動新線程。
         *
         */
    }
   }
   /**
       *  每個線程執(zhí)行時都具有一定的優(yōu)先級,優(yōu)先級高的線程獲得較多的執(zhí)行機(jī)會,而優(yōu)先級低的則反之。每個子線程默認(rèn)的優(yōu)先級是0.5. NSThread 通過如下代碼演示優(yōu)先級。
    */
  //    NSLog(@"UI線程的優(yōu)先級為:%g",[NSThread threadPriority]);
  //    //創(chuàng)建第一個線程對象
  //    NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
  //    //設(shè)置第一個線程對象的名字
  //    thread1.name = @"線程A";
  //    NSLog(@"線程A的優(yōu)先級為:%g",thread1.threadPriority);
  //    //設(shè)置使用最低優(yōu)先級
  //    thread1.threadPriority = 0.0;
  //    //創(chuàng)建第二個線程對象
  //    NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
  //    //設(shè)置第二個線程對象的名字
  //    thread2.name = @"線程B";
  //    NSLog(@"線程B的優(yōu)先級為:%g",thread2.threadPriority);
  //    //設(shè)置使用最高優(yōu)先級
  //    thread2.threadPriority = 1.0;
  //    //啟動兩個線程
  //    [thread1 start];
  //    [thread2 start];
  }
  - (void)run {
       for (int i=0; i<100; i++) {
    //        if ([NSThread currentThread].isCancelled) {
    //            //終止當(dāng)前正在執(zhí)行的線程
    //            [NSThread exit];
    //        }
     NSLog(@"------%@-----%d",[NSThread currentThread].name,i);
    //沒執(zhí)行一次,線程暫停0.5秒
    //        [NSThread sleepForTimeInterval:0.5];
    
    //使當(dāng)前線程暫停一段時間,或者暫停到某個時刻
    //        + (void)sleepForTimeInterval:(NSTimeInterval)time;
    //        + (void)sleepUntilDate:(NSDate *)date;
   }
  }
   //- (IBAction)cancelThread:(id)sender {
   //    //取消thread線程,調(diào)用該方法,調(diào)用該方法后,thread的isCancelled方法將會返回NO
   //    [thread cancel];
   //}
  @end

相關(guān)代碼下載:OC-NSThread

2.NSOperation和NSOperationQueue

NSOperation是一個抽象基類,基本沒有什么實際使用價值。我們使用最多的是系統(tǒng)封裝好的NSInvocationOperation和NSBlockOperation。

(1)NSOperation

NSOperation 是蘋果公司對 GCD 的封裝,完全面向?qū)ο?,所以使用起來更好理解?大家可以看到 NSOperation 和 NSOperationQueue 分別對應(yīng) GCD 的 任務(wù) 和 隊列 。操作步驟也很好理解:

  • 將要執(zhí)行的任務(wù)封裝到一個 NSOperation 對象中。
  • 將此任務(wù)添加到一個 NSOperationQueue 對象中。
NSOperation * operation = [[NSOperation alloc]init];
//開始執(zhí)行
[operation start];
//取消執(zhí)行
[operation cancel];
//執(zhí)行結(jié)束后調(diào)用的Block
[operation setCompletionBlock:^{
    NSLog(@"執(zhí)行結(jié)束");
}];

NSInvocationOperation的使用方式和給Button添加事件比較相似,需要一個對象和一個Selector。使用方法非常簡單。

添加任務(wù)

值得說明的是,NSOperation 只是一個抽象類,所以不能封裝任務(wù)。但它有 2 個子類用于封裝任務(wù)。分別是:NSInvocationOperation 和 NSBlockOperation 。創(chuàng)建一個 Operation 后,需要調(diào)用 start 方法來啟動任務(wù),它會 默認(rèn)在當(dāng)前隊列同步執(zhí)行。當(dāng)然你也可以在中途取消一個任務(wù),只需要調(diào)用其 cancel 方法即可。

//創(chuàng)建
NSInvocationOperation * invo = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(testNSInvocationOperation) object:nil];
//執(zhí)行
[invo start];

 - (void)testNSOperation {
    NSLog(@"我在第%@個線程",[NSThread currentThread]);
 }

我們可以看到NSInvocationOperation其實是同步執(zhí)行的,因此單獨使用的話,這個東西也沒有什么卵用,它需要配合我們后面介紹的NSOperationQueue去使用才能實現(xiàn)多線程調(diào)用,所以這里我們只需要記住有這么一個東西就行了。參考文章:NSOperation

(2)NSBlockOperation
//1.創(chuàng)建NSBlockOperation對象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];

 //2.開始任務(wù)
 [operation start];

參考:NSBlockOperation

3.GCD

Grand Central Dispatch,聽名字就霸氣。它是蘋果為多核的并行運算提出的解決方案,所以會自動合理地利用更多的CPU內(nèi)核(比如雙核、四核),最重要的是它會自動管理線程的生命周期(創(chuàng)建線程、調(diào)度任務(wù)、銷毀線程),完全不需要我們管理,我們只需要告訴干什么就行。同時它使用的也是 c語言,不過由于使用了 Block(Swift里叫做閉包),使得使用起來更加方便,而且靈活。

首先來了解兩個基本概念:任務(wù)和隊列
  • 任務(wù):及操作,你想要干什么,說白了就是一段代碼,在 GCD 中就是一個 Block,所以添加任務(wù)十分方便。任務(wù)有兩種執(zhí)行方式: 同步執(zhí)行 和 異步執(zhí)行,他們之間的區(qū)別是 是否會創(chuàng)建新的線程。

     同步(sync) 和 異步(async) 的主要區(qū)別在于會不會阻塞當(dāng)前線程,直到 Block 中的任務(wù)執(zhí)行完畢!
     如果是 同步(sync) 操作,它會阻塞當(dāng)前線程并等待 Block 中的任務(wù)執(zhí)行完畢,然后當(dāng)前線程才會繼續(xù)往下運行。
     如果是 異步(async)操作,當(dāng)前線程會直接往下執(zhí)行,它不會阻塞當(dāng)前線程。
    
  • 隊列:用于存放任務(wù)。一共有兩種隊列,串行隊列和并行隊列(先入先出)。

口訣:

  • 同步不開異步開,串行開一條,并行開多條
(1)創(chuàng)建隊列

主隊列:
這是一個特殊的 串行隊列。什么是主隊列,大家都知道吧,它用于刷新 UI,任何需要刷新 UI 的工作都要在主隊列執(zhí)行,所以一般耗時的任務(wù)都要放到別的線程執(zhí)行。

  dispatch_queue_t queue = ispatch_get_main_queue();

自己創(chuàng)建的隊列:
第一個參數(shù)是標(biāo)識符,第二個參數(shù)用來表示創(chuàng)建的對壘是串行還是并行。

//創(chuàng)建串行隊列 - DISPATCH_QUEUE_SERIAL或NULL
serialQueue = dispatch_queue_create("fkjava.queue", DISPATCH_QUEUE_SERIAL);
//創(chuàng)建并發(fā)隊列 - DISPATCH_QUEUE_CONCURRENT
concurrentQueue = dispatch_queue_create("fkjava.queue", DISPATCH_QUEUE_CONCURRENT);

全局并發(fā)隊列:
只要是并行任務(wù)一般都加入到這個隊列。這是系統(tǒng)提供的一個并發(fā)隊列。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

(2)創(chuàng)建任務(wù)

  • 同步任務(wù):會阻塞當(dāng)前線程(sync)
    //將代碼塊以同步方式提交給指定隊列,該隊列底層的線程池將負(fù)責(zé)執(zhí)行該代碼塊
    dispatch_sync(<#dispatch_queue_t queue#>, <#^(void)block#>)
    //將函數(shù)以同步方式提交給指定隊列,該隊列底層的線程池將負(fù)責(zé)執(zhí)行該函數(shù)
    dispatch_sync_f(<#dispatch_queue_t queue#>, <#void *context#>, <#dispatch_function_t work#>)

  • 異步任務(wù):不會阻塞當(dāng)前線程 (async)

     //將代碼塊以異步方式提交給指定隊列,該隊列底層的線程池將負(fù)責(zé)執(zhí)行該代碼塊
     dispatch_async(<#dispatch_queue_t queue#>, <#^(void)block#>)
     //將函數(shù)以異步方式提交給指定隊列,該隊列底層的線程池將負(fù)責(zé)執(zhí)行該函數(shù)
     dispatch_async_f(<#dispatch_queue_t queue#>, <#void *context#>, <#dispatch_function_t work#>)
    

相關(guān)代碼下載:OC-GCD

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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