iOS 多線程

GCD總結(jié)
NSOperation總結(jié)
iOS面試題(三)多線程開發(fā) - 簡(jiǎn)書
iOS基礎(chǔ)深入補(bǔ)完計(jì)劃--多線程(面試題)匯總 - CocoaChina_讓移動(dòng)開發(fā)更簡(jiǎn)單
可能碰到的iOS筆試面試題(18)--多線程 - 簡(jiǎn)書

進(jìn)程

  • 在系統(tǒng)中正在運(yùn)行的一個(gè)應(yīng)用程序沸移,每個(gè)進(jìn)程之間是獨(dú)立的啰脚,每個(gè)進(jìn)程均運(yùn)行在其專用且受保護(hù)的內(nèi)存空間內(nèi)
  • 進(jìn)程用于指代一個(gè)可執(zhí)行程序缝彬,他可以包含多個(gè)線程(蘋果官方解釋)

線程

  • 1個(gè)進(jìn)程想要執(zhí)行任務(wù),必須得有線程(每一個(gè)進(jìn)程至少要有1條線程),線程是進(jìn)程的基本執(zhí)行單元闽巩,一個(gè)進(jìn)程(程序)的所有任務(wù)都在線程中執(zhí)行线得。
  • 線程用于指代一個(gè)獨(dú)立執(zhí)行的代碼路徑(蘋果官方解釋)

主線程

  • 一個(gè)iOS程序運(yùn)行后,默認(rèn)會(huì)開啟1條線程肄程,稱為“主線程”或者“UI線程”锣吼。
  • 作用
    • 顯示/刷新UI界面
    • 處理UI事件
  • 使用注意
    • 別將比較耗時(shí)的操作放在主線程中,操作影響UI的流暢度蓝厌,給用戶一種卡的體驗(yàn)

多線程

  • 概念
    • 一個(gè)程序開啟多條線程玄叠,每條線程可以并行(同時(shí))執(zhí)行不同的任務(wù)
  • 原理
    • 多個(gè)線程并發(fā)執(zhí)行,其實(shí)質(zhì)是CPU快速地在多條線程之間調(diào)度(切換)
    • 當(dāng)CPU調(diào)度線程的時(shí)間足夠快拓提,就會(huì)造成多線程并發(fā)執(zhí)行的假象

串行

  • 定義
    • 順序執(zhí)行,一個(gè)個(gè)執(zhí)行读恃,在同一時(shí)間內(nèi),1個(gè)線程只能執(zhí)行一個(gè)任務(wù) 代态。

并行

  • 定義
    • 可以一起執(zhí)行寺惫,同一時(shí)間內(nèi),一個(gè)進(jìn)程可以執(zhí)行多個(gè)任務(wù)多線程:一個(gè)線程中可以開啟多條線程胆数,每條線程可以并發(fā)(同時(shí))執(zhí)行不同的任務(wù)肌蜻。
  • 優(yōu)點(diǎn):
    • 提高程序的執(zhí)行效率,能適當(dāng)提高資源利用率必尼。
  • 缺點(diǎn)
    • 開啟線程需要占一定的內(nèi)存空間蒋搜,如果開啟大量線程,CPU會(huì)在N多線程之間調(diào)度判莉,CPU會(huì)累死豆挽,消耗大量的CPU資源,降低程序性能券盅,程序設(shè)計(jì)更復(fù)雜帮哈。
      原理:同一時(shí)間內(nèi),CPU只能處理一條線程锰镀,只有1條線程在工作娘侍。多線程并發(fā)執(zhí)行,其實(shí)是CPU快速的在多條線程之間切換泳炉。

多線程的方式

1.NSThread

  • 優(yōu)點(diǎn):NSThread 輕量級(jí)最低憾筏,相對(duì)簡(jiǎn)單。

  • 缺點(diǎn):手動(dòng)管理所有的線程活動(dòng)花鹅,如生命周期氧腰、線程同步、睡眠等刨肃。

  • 新建

    //創(chuàng)建線程方式1
    //創(chuàng)建
    NSThread * th = [[NSThread alloc] initWithTarget:self selector:@selector(nsThreadMethod) object:nil];
    
    //創(chuàng)建線程方式2
    //創(chuàng)建并啟動(dòng)
    [NSThread detachNewThreadSelector:@selector(nsThreadMethod) toTarget:self withObject:nil];
    
    //隱式創(chuàng)建方式
     [self performSelectorInBackground:@selector(nsThreadMethod) withObject:nil];
    
    //不傳遞參數(shù)指定函數(shù)在當(dāng)前線程執(zhí)行
    [self performSelector:@selector(doSomething)];
    //傳遞參數(shù)指定函數(shù)在當(dāng)前線程執(zhí)行
    [self performSelector: @selector(doSomething:) withObject:tempStr];
    //傳遞參數(shù)指定函數(shù)2秒后在當(dāng)前線程執(zhí)行
    [self performSelector:@selector(doSomething:) withObject:tempStr afterDelay:2.0];
    
    
  • 就緒

    • 將線程放入線程池中
  • 運(yùn)行

     [th start];
    
  • 阻塞

    //休眠2秒
    [ NSThread sleepForTimeInterval:2];
    //休眠到指定時(shí)間
    [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:5.0]];
    
  • 銷毀

     //結(jié)束線程
    [NSThread exit];
    
  • 指定在特定線程執(zhí)行

     //在其他線程中指定在主線程執(zhí)行
    [self performSelectorOnMainThread:@selector(doSomething:) withObject:tempStr waitUntilDone:YES];
    //在主線程指定在后臺(tái)線程執(zhí)行
    [self performSelectorInBackground:@selector(doSomething:) withObject:tempStr];
    //在主線程中指定某個(gè)特定線程執(zhí)行
    [self performSelector:@selector(doSomething:)  onThread:newThread withObject:tempStr waitUntilDone:YES];
    
  • 線程常用屬性

     //獲取主線程
     [NSThread mainThread];
     //獲取當(dāng)前線程
     [NSThread currentThread];
     //設(shè)置線程名字
     [newThread setName:@"thread - 1"];
     //設(shè)置線程優(yōu)先級(jí)
     [newThread setThreadPriority:1.0];
     //IOS 8 之后 推薦使用下面這種方式設(shè)置線程優(yōu)先級(jí)
     //NSQualityOfServiceUserInteractive:最高優(yōu)先級(jí)古拴,用于用戶交互事件
     //NSQualityOfServiceUserInitiated:次高優(yōu)先級(jí),用于用戶需要馬上執(zhí)行的事件
     //NSQualityOfServiceDefault:默認(rèn)優(yōu)先級(jí)真友,主線程和沒(méi)有設(shè)置優(yōu)先級(jí)的線程都默認(rèn)為這個(gè)優(yōu)先級(jí)
     //NSQualityOfServiceUtility:普通優(yōu)先級(jí)黄痪,用于普通任務(wù)
     //NSQualityOfServiceBackground:最低優(yōu)先級(jí),用于不重要的任務(wù)
     [newThread setQualityOfService:NSQualityOfServiceUtility];
     //判斷線程是否是主線程
     [newThread isMainThread];
     //線程狀態(tài)
     //是否已經(jīng)取消
     [newThread isCancelled];
     //是否已經(jīng)結(jié)束
     [newThread isFinished];
     //是否正在執(zhí)行
     [newThread isExecuting];
    
  • 線程的同步

    • 線程同步就是為了解決多線程同時(shí)訪問(wèn)公共共享數(shù)據(jù)盔然,而導(dǎo)致數(shù)據(jù)錯(cuò)亂的問(wèn)題满力,然后使用同步的方式讓公共數(shù)據(jù)同一時(shí)間內(nèi)只能被一個(gè)線程訪問(wèn)來(lái)避免數(shù)據(jù)錯(cuò)亂的問(wèn)題
    • 實(shí)現(xiàn)線程同步的幾種方式
      • 1.@synchronized(對(duì)象)關(guān)鍵字
        -(void)taskRun
        {
          while (count>0) {
              @synchronized(self) { // 需要鎖定的代碼
                  [NSThread sleepForTimeInterval:0.1];
                  count--;
                  NSLog(@"threadName:%@ count:%d ",[NSThread currentThread].name, count);
              }
          }
        
        }
        
      • 2.NSLock同步鎖
        -(void)taskRun
        {
            while (count>0) {
                @synchronized(self) { // 需要鎖定的代碼
                    [NSThread sleepForTimeInterval:0.1];
                    count--;
                    NSLog(@"threadName:%@ count:%d ",[NSThread currentThread].name, count);
                }
            }
        
        }
        
      • 3.NSCondition同步鎖和線程檢查器
        //鎖主要為了當(dāng)檢測(cè)條件時(shí)保護(hù)數(shù)據(jù)源焕参,執(zhí)行條件引發(fā)的任務(wù);線程檢查器主要是根據(jù)條件決定是否繼續(xù)運(yùn)行線程油额,即線程是否被阻塞。先創(chuàng)建一個(gè)NSCondition對(duì)象
        condition=[[NSCondition alloc]init];
        
        //使用同步鎖的方式和NSLock相似
        -(void)taskRun
        {
           while (count>0) {
               [condition lock];
               [NSThread sleepForTimeInterval:0.1];
               count--;
               NSLog(@"threadName:%@ count:%d ",[NSThread currentThread].name, count);
               [condition unlock];
           }
        
        }
        //NSCondition可以讓線程進(jìn)行等待刻帚,然后獲取到CPU發(fā)信號(hào)告訴線程不用在等待潦嘶,可以繼續(xù)執(zhí)行,上述的例子我們稍作修改崇众,我們讓線程三專門用于發(fā)送信號(hào)源
           NSThread *thread1=[[NSThread alloc]initWithTarget:self selector:@selector(taskRun) object:nil];
           thread1.name=@"thread-1";
        
           NSThread *thread2=[[NSThread alloc]initWithTarget:self selector:@selector(taskRun) object:nil];
           thread2.name=@"thread-2";
        
           NSThread *thread3=[[NSThread alloc]initWithTarget:self selector:@selector(taskRun1) object:nil];
           thread3.name=@"thread-3";
        
           [thread1 start];
           [thread2 start];
           [thread3 start];
        //taskRun1函數(shù)用于發(fā)送信號(hào)源
        -(void)taskRun1
        {
           while (YES) {
               [condition lock];
               [NSThread sleepForTimeInterval:2];
               [condition signal];
               [condition unlock];
           }
        }
        //taskRun函數(shù) 用于執(zhí)行對(duì)count的操作
        -(void)taskRun
        {
        
           while (count>0) {
               [condition lock];
               [condition wait];
               [NSThread sleepForTimeInterval:0.1];
               count--;
               NSLog(@"threadName:%@ count:%d ",[NSThread currentThread].name, count);
               [condition unlock];
           }
        
        }       
        

2.GCD

  • 優(yōu)點(diǎn):最高效掂僵,避開并發(fā)陷阱。

  • 缺點(diǎn):基于C實(shí)現(xiàn)

  • 隊(duì)列

    • 作用:用來(lái)存放任務(wù)顷歌。
      dispatch_queue_t  queue =  dispatch_queue_create("123", DISPATCH_QUEUE_SERIAL);
      //“123”:隊(duì)列標(biāo)示锰蓬。
      //DISPATCH_QUEUE_SERIAL:串行
      //DISPATCH_QUEUE_CONCURRENT:并行
      
      
    • UI主線程隊(duì)列 main queue
      • dispatch_get_main_queue()
    • 串行隊(duì)列
      • 順序,一個(gè)個(gè)執(zhí)行眯漩。
    • 并行隊(duì)列
      • 同時(shí)執(zhí)行很多任務(wù)芹扭。
    • 全局隊(duì)列
      //第一個(gè)參數(shù):線程的優(yōu)先級(jí),有高赦抖,默認(rèn)舱卡,低,后臺(tái)队萤,由高到低轮锥。
      //第二個(gè)參數(shù):備用,默認(rèn)0,不能改成其他
      dispatch_queue_t  queue = dispatch_get_global_queue(0, 0);
      
      • 全局隊(duì)列和并發(fā)隊(duì)列的區(qū)別:
        • 全局隊(duì)列沒(méi)有名稱要尔,并發(fā)隊(duì)列有名稱舍杜。
        • 全局隊(duì)列是所有應(yīng)用程序共享。
  • 任務(wù)

    • 執(zhí)行的操作
        dispatch_sync(queue, ^{
            //code
        });
        //queue:隊(duì)列
        //dispatch_sync:同步
        //dispatch_async:異步
      
    • 同步
      • dispatch_sync赵辕,不開線程既绩,在當(dāng)前線程中執(zhí)行,馬上執(zhí)行
    • 異步
      • dispatch_async匆帚,開辟線程熬词,在另一個(gè)線程中執(zhí)行,稍后執(zhí)行
  • 使用

    • 將任務(wù)添加隊(duì)列中吸重,GCD會(huì)自定將隊(duì)列中的任務(wù)取出互拾,放到對(duì)應(yīng)線程中執(zhí)行,任務(wù)的取出遵循隊(duì)列的FIFO原則:先進(jìn)先出嚎幸,后進(jìn)后出颜矿。
    • 延時(shí)線程
      dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
              //code
      });
      
    • 線程調(diào)度組
      • dispatch_group_enter、dispatch_group_leave

      • dispatch_group_enter 標(biāo)志著一個(gè)任務(wù)追加到 group嫉晶,執(zhí)行一次骑疆,相當(dāng)于 group 中未執(zhí)行完畢任務(wù)數(shù)+1

      • dispatch_group_leave 標(biāo)志著一個(gè)任務(wù)離開了 group田篇,執(zhí)行一次,相當(dāng)于 group 中未執(zhí)行完畢任務(wù)數(shù)-1箍铭。

      • 當(dāng) group 中未執(zhí)行完畢任務(wù)數(shù)為0的時(shí)候泊柬,才會(huì)使dispatch_group_wait解除阻塞,以及執(zhí)行追加到dispatch_group_notify中的任務(wù)诈火。

           //    實(shí)例化一個(gè)調(diào)度組
               dispatch_group_t  group = dispatch_group_create();
           //    創(chuàng)建隊(duì)列
               dispatch_queue_t queue2 = dispatch_get_global_queue(0, 0);
           //    任務(wù)添加到隊(duì)列queue
               dispatch_group_enter(group);
               dispatch_group_async(group, queue2, ^{
                   //code1
                 dispatch_group_leave(group);
               });
        
               dispatch_group_async(group, queue2, ^{
                   //code2
               });
        
               dispatch_group_async(group, queue2, ^{
                   //code3
               });
               //獲取所有調(diào)度組里面的異步任務(wù)完成的通知
               dispatch_group_notify(group, queue2, ^{
                   //code4
               });
        
    • 一次性線程
      static dispatch_once_t onceToken;
          dispatch_once(&onceToken, ^{
              _instance = [[GetToken alloc] init];
      });
      
    • 定時(shí)線程
      //創(chuàng)建并行隊(duì)列
      dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); // 并行queue
      //創(chuàng)建一個(gè)GCD定時(shí)器
      dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, aQueue);
      //設(shè)置定時(shí)器的參數(shù)
      //第一個(gè)參數(shù):要給哪個(gè)設(shè)置定時(shí)器兽赁。
      //第二個(gè)參數(shù):開始時(shí)間
      //第三個(gè)參數(shù):間隔時(shí)間
      //第四個(gè)參數(shù):精準(zhǔn)度,一般為0.
      dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
      //設(shè)置定時(shí)器調(diào)用的方法冷守。
      dispatch_source_set_event_handler(timer, ^{
         
      });
      啟動(dòng)
      dispatch_resume(timer);
      
      
    • 暫停線程
      • 暫停當(dāng)前線程(阻塞當(dāng)前線程)刀崖,等待指定的 group 中的任務(wù)執(zhí)行完成后,才會(huì)往下繼續(xù)執(zhí)行拍摇。
         /**
         * 隊(duì)列組 dispatch_group_wait
         */
        - (void)groupWait {
            NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印當(dāng)前線程
            NSLog(@"group---begin");
        
            dispatch_group_t group =  dispatch_group_create();
        
            dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                // 追加任務(wù)1
                for (int i = 0; i < 2; ++i) {
                    [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
                    NSLog(@"1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
                }
            });
        
            dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                // 追加任務(wù)2
                for (int i = 0; i < 2; ++i) {
                    [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
                    NSLog(@"2---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
                }
            });
        
            // 等待上面的任務(wù)全部完成后亮钦,會(huì)往下繼續(xù)執(zhí)行(會(huì)阻塞當(dāng)前線程)
            dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
        
            NSLog(@"group---end");
        }
        
        
    • 柵欄線程
      • 我們有時(shí)需要異步執(zhí)行兩組操作,而且第一組操作執(zhí)行完之后充活,才能開始執(zhí)行第二組操作蜂莉。這樣我們就需要一個(gè)相當(dāng)于柵欄一樣的一個(gè)方法將兩組異步執(zhí)行的操作組給分割起來(lái),當(dāng)然這里的操作組里可以包含一個(gè)或多個(gè)任務(wù)堪唐。這就需要用到dispatch_barrier_async方法在兩個(gè)操作組間形成柵欄巡语。dispatch_barrier_async函數(shù)會(huì)等待前邊追加到并發(fā)隊(duì)列中的任務(wù)全部執(zhí)行完畢之后,再將指定的任務(wù)追加到該異步隊(duì)列中淮菠。然后在dispatch_barrier_async函數(shù)追加的任務(wù)執(zhí)行完畢之后男公,異步隊(duì)列才恢復(fù)為一般動(dòng)作,接著追加任務(wù)到該異步隊(duì)列并開始執(zhí)行
         /**
          * 柵欄方法 dispatch_barrier_async
          */
         - (void)barrier {
             dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
        
             dispatch_async(queue, ^{
                 // 追加任務(wù)1
                 for (int i = 0; i < 2; ++i) {
                     [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
                     NSLog(@"1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
                 }
             });
             dispatch_async(queue, ^{
                 // 追加任務(wù)2
                 for (int i = 0; i < 2; ++i) {
                     [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
                     NSLog(@"2---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
                 }
             });
        
             dispatch_barrier_async(queue, ^{
                 // 追加任務(wù) barrier
                 for (int i = 0; i < 2; ++i) {
                     [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
                     NSLog(@"barrier---%@",[NSThread currentThread]);// 打印當(dāng)前線程
                 }
             });
        
             dispatch_async(queue, ^{
                 // 追加任務(wù)3
                 for (int i = 0; i < 2; ++i) {
                     [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
                     NSLog(@"3---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
                 }
             });
             dispatch_async(queue, ^{
                 // 追加任務(wù)4
                 for (int i = 0; i < 2; ++i) {
                     [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
                     NSLog(@"4---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
                 }
             });
         }
             /*
         2018-02-23 20:48:18.297745+0800 YSC-GCD-demo[20188:5059274] 1---<NSThread: 0x600000079d80>{number = 4, name = (null)}
         2018-02-23 20:48:18.297745+0800 YSC-GCD-demo[20188:5059273] 2---<NSThread: 0x600000079e00>{number = 3, name = (null)}
         2018-02-23 20:48:20.301139+0800 YSC-GCD-demo[20188:5059274] 1---<NSThread: 0x600000079d80>{number = 4, name = (null)}
         2018-02-23 20:48:20.301139+0800 YSC-GCD-demo[20188:5059273] 2---<NSThread: 0x600000079e00>{number = 3, name = (null)}
         2018-02-23 20:48:22.306290+0800 YSC-GCD-demo[20188:5059274] barrier---<NSThread: 0x600000079d80>{number = 4, name = (null)}
         2018-02-23 20:48:24.311655+0800 YSC-GCD-demo[20188:5059274] barrier---<NSThread: 0x600000079d80>{number = 4, name = (null)}
         2018-02-23 20:48:26.316943+0800 YSC-GCD-demo[20188:5059273] 4---<NSThread: 0x600000079e00>{number = 3, name = (null)}
         2018-02-23 20:48:26.316956+0800 YSC-GCD-demo[20188:5059274] 3---<NSThread: 0x600000079d80>{number = 4, name = (null)}
         2018-02-23 20:48:28.320660+0800 YSC-GCD-demo[20188:5059273] 4---<NSThread: 0x600000079e00>{number = 3, name = (null)}
         2018-02-23 20:48:28.320649+0800 YSC-GCD-demo[20188:5059274] 3---<NSThread: 0x600000079d80>{number = 4, name = (null)}
        
             */
        
    • 快速迭代方法
      • 通常我們會(huì)用 for 循環(huán)遍歷合陵,但是 GCD 給我們提供了快速迭代的函數(shù)dispatch_apply枢赔。dispatch_apply按照指定的次數(shù)將指定的任務(wù)追加到指定的隊(duì)列中,并等待全部隊(duì)列執(zhí)行結(jié)束
      • 如果是在串行隊(duì)列中使用 dispatch_apply拥知,那么就和 for 循環(huán)一樣踏拜,按順序同步執(zhí)行〉吞蓿可這樣就體現(xiàn)不出快速迭代的意義了速梗。
      • 我們可以利用并發(fā)隊(duì)列進(jìn)行異步執(zhí)行。比如說(shuō)遍歷 0~5 這6個(gè)數(shù)字襟齿,for 循環(huán)的做法是每次取出一個(gè)元素姻锁,逐個(gè)遍歷。dispatch_apply 可以 在多個(gè)線程中同時(shí)(異步)遍歷多個(gè)數(shù)字猜欺。
      • 還有一點(diǎn)位隶,無(wú)論是在串行隊(duì)列,還是異步隊(duì)列中开皿,dispatch_apply 都會(huì)等待全部任務(wù)執(zhí)行完畢涧黄,這點(diǎn)就像是同步操作篮昧,也像是隊(duì)列組中的 dispatch_group_wait方法。
        /**
        * 快速迭代方法 dispatch_apply
        */
        - (void)apply {
           dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        
           NSLog(@"apply---begin");
           dispatch_apply(6, queue, ^(size_t index) {
               NSLog(@"%zd---%@",index, [NSThread currentThread]);
           });
           NSLog(@"apply---end");
        }      
            /*
        2018-02-23 22:03:18.475499+0800 YSC-GCD-demo[20470:5176805] apply---begin
        2018-02-23 22:03:18.476672+0800 YSC-GCD-demo[20470:5177035] 1---<NSThread: 0x60000027b8c0>{number = 3, name = (null)}
        2018-02-23 22:03:18.476693+0800 YSC-GCD-demo[20470:5176805] 0---<NSThread: 0x604000070640>{number = 1, name = main}
        2018-02-23 22:03:18.476704+0800 YSC-GCD-demo[20470:5177037] 2---<NSThread: 0x604000276800>{number = 4, name = (null)}
        2018-02-23 22:03:18.476735+0800 YSC-GCD-demo[20470:5177036] 3---<NSThread: 0x60000027b800>{number = 5, name = (null)}
        2018-02-23 22:03:18.476867+0800 YSC-GCD-demo[20470:5177035] 4---<NSThread: 0x60000027b8c0>{number = 3, name = (null)}
        2018-02-23 22:03:18.476867+0800 YSC-GCD-demo[20470:5176805] 5---<NSThread: 0x604000070640>{number = 1, name = main}
        2018-02-23 22:03:18.477038+0800 YSC-GCD-demo[20470:5176805] apply---end
             */
        
    • semaphore 加鎖
          /**
      * 線程安全:使用 semaphore 加鎖
      * 初始化火車票數(shù)量笋妥、賣票窗口(線程安全)懊昨、并開始賣票
      */
      - (void)initTicketStatusSave {
         NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印當(dāng)前線程
         NSLog(@"semaphore---begin");
      
         semaphoreLock = dispatch_semaphore_create(1);
      
         self.ticketSurplusCount = 50;
      
         // queue1 代表北京火車票售賣窗口
         dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
         // queue2 代表上海火車票售賣窗口
         dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
      
         __weak typeof(self) weakSelf = self;
         dispatch_async(queue1, ^{
             [weakSelf saleTicketSafe];
         });
      
         dispatch_async(queue2, ^{
             [weakSelf saleTicketSafe];
         });
      }
      
      /**
      * 售賣火車票(線程安全)
      */
      - (void)saleTicketSafe {
         while (1) {
             // 相當(dāng)于加鎖
             dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);
      
             if (self.ticketSurplusCount > 0) {  //如果還有票春宣,繼續(xù)售賣
                 self.ticketSurplusCount--;
                 NSLog(@"%@", [NSString stringWithFormat:@"剩余票數(shù):%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
                 [NSThread sleepForTimeInterval:0.2];
             } else { //如果已賣完疚颊,關(guān)閉售票窗口
                 NSLog(@"所有火車票均已售完");
      
                 // 相當(dāng)于解鎖
                 dispatch_semaphore_signal(semaphoreLock);
                 break;
             }
      
             // 相當(dāng)于解鎖
             dispatch_semaphore_signal(semaphoreLock);
         }
      }
      
  • 總結(jié)

    • 1.串行隊(duì)列同步執(zhí)行:不開線程,在原來(lái)線程里面一個(gè)一個(gè)順序執(zhí)行信认。
    • 2.串行隊(duì)列異步執(zhí)行:開一條線程,在新來(lái)線程里面一個(gè)一個(gè)順序執(zhí)行均抽。
    • 3.并行隊(duì)列異步執(zhí)行:開多個(gè)線程嫁赏,并發(fā)執(zhí)行
    • 4.并行隊(duì)列同步執(zhí)行:不開線程,在原來(lái)線程里面一個(gè)一個(gè)順序執(zhí)行油挥。
    • 1.開不開線程潦蝇,由執(zhí)行任務(wù)方法決定,同步不開線程深寥,異步開線程攘乒。
    • 2.開多少線程,由隊(duì)列決定惋鹅,串行最多開一個(gè)線程则酝,并發(fā)可以開多個(gè)線程,具體開多少個(gè)闰集,GCD底層決定沽讹,開發(fā)者不能控制。


      3bed772b.png

3.NSOperation

  • 優(yōu)點(diǎn):自帶線程周期管理武鲁,操作上可更注重自己邏輯爽雄。
  • 缺點(diǎn):面向?qū)ο蟮某橄箢悾荒軐?shí)現(xiàn)它或者使用它定義好的兩個(gè)子類:NSInvocationOperation 和 NSBlockOperation沐鼠。
  • NSOperation挚瘟、NSOperationQueue 簡(jiǎn)介
    • 是蘋果提供給我們的一套多線程解決方案。實(shí)際上 NSOperation饲梭、NSOperationQueue 是基于 GCD 更高一層的封裝乘盖,完全面向?qū)ο蟆5潜?GCD 更簡(jiǎn)單易用排拷、代碼可讀性也更高侧漓。
  • 為什么要使用 NSOperation、NSOperationQueue监氢?
    • 可添加完成的代碼塊布蔗,在操作完成后執(zhí)行藤违。
    • 添加操作之間的依賴關(guān)系,方便的控制執(zhí)行順序纵揍。
    • 設(shè)定操作執(zhí)行的優(yōu)先級(jí)顿乒。
    • 可以很方便的取消一個(gè)操作的執(zhí)行。
    • 使用 KVO 觀察對(duì)操作執(zhí)行狀態(tài)的更改:isExecuteing泽谨、isFinished璧榄、isCancelled。
  • 操作(Operation)
    • 執(zhí)行操作的意思吧雹,換句話說(shuō)就是你在線程中執(zhí)行的那段代碼骨杂。
    • 在 GCD 中是放在 block 中的。在 NSOperation 中雄卷,我們使用 NSOperation 子類 NSInvocationOperation搓蚪、NSBlockOperation,或者自定義子類來(lái)封裝操作丁鹉。
  • 操作隊(duì)列(Operation Queues)
    • 這里的隊(duì)列指操作隊(duì)列妒潭,即用來(lái)存放操作的隊(duì)列。不同于 GCD 中的調(diào)度隊(duì)列 FIFO(先進(jìn)先出)的原則揣钦。NSOperationQueue 對(duì)于添加到隊(duì)列中的操作雳灾,首先進(jìn)入準(zhǔn)備就緒的狀態(tài)(就緒狀態(tài)取決于操作之間的依賴關(guān)系),然后進(jìn)入就緒狀態(tài)的操作的開始執(zhí)行順序(非結(jié)束執(zhí)行順序)由操作之間相對(duì)的優(yōu)先級(jí)決定(優(yōu)先級(jí)是操作對(duì)象自身的屬性)冯凹。
    • 操作隊(duì)列通過(guò)設(shè)置最大并發(fā)操作數(shù)(maxConcurrentOperationCount)來(lái)控制并發(fā)谎亩、串行。
    • NSOperationQueue 為我們提供了兩種不同類型的隊(duì)列:主隊(duì)列和自定義隊(duì)列谈竿。主隊(duì)列運(yùn)行在主線程之上团驱,而自定義隊(duì)列在后臺(tái)執(zhí)行。
  • 使用步驟
    • NSOperation 需要配合 NSOperationQueue 來(lái)實(shí)現(xiàn)多線程空凸。因?yàn)槟J(rèn)情況下嚎花,NSOperation 單獨(dú)使用時(shí)系統(tǒng)同步執(zhí)行操作,配合 NSOperationQueue 我們能更好的實(shí)現(xiàn)異步執(zhí)行呀洲。
    • 1.創(chuàng)建操作:先將需要執(zhí)行的操作封裝到一個(gè) NSOperation 對(duì)象中紊选。
    • 2.創(chuàng)建隊(duì)列:創(chuàng)建 NSOperationQueue 對(duì)象。
    • 3.將操作加入到隊(duì)列中:將 NSOperation 對(duì)象添加到 NSOperationQueue 對(duì)象中道逗。
  • 基本使用
    • 使用子類 NSInvocationOperation
            /**
       * 使用子類 NSInvocationOperation
       */
      - (void)useInvocationOperation {
      
          // 1.創(chuàng)建 NSInvocationOperation 對(duì)象
          NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];
      
          // 2.調(diào)用 start 方法開始執(zhí)行操作
          [op start];
      }
      
      /**
       * 任務(wù)1
       */
      - (void)task1 {
          for (int i = 0; i < 2; i++) {
              [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
              NSLog(@"1---%@", [NSThread currentThread]); // 打印當(dāng)前線程
          }
      }
      
      
    • 使用子類 NSBlockOperation
      /**
       * 使用子類 NSBlockOperation
       */
      - (void)useBlockOperation {
      
          // 1.創(chuàng)建 NSBlockOperation 對(duì)象
          NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
              for (int i = 0; i < 2; i++) {
                  [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
                  NSLog(@"1---%@", [NSThread currentThread]); // 打印當(dāng)前線程
              }
          }];
      //追加
        [op addExecutionBlock:^{
              //code
          }];
      
          // 2.調(diào)用 start 方法開始執(zhí)行操作
          [op start];
      }
      
      
    • 自定義繼承自 NSOperation 的子類兵罢,通過(guò)實(shí)現(xiàn)內(nèi)部相應(yīng)的方法來(lái)封裝操作。
  • 隊(duì)列
    • 主隊(duì)列
      // 主隊(duì)列獲取方法
      NSOperationQueue * queue = [NSOperationQueue mainQueue];
      
    • 非主隊(duì)列(默認(rèn)是并發(fā)隊(duì)列)
      // 自定義隊(duì)列創(chuàng)建方法
      NSOperationQueue *queue = [[NSOperationQueue alloc] init];
      //獲取當(dāng)前隊(duì)列
      NSOperationQueue * queue = [NSOperationQueue currentQueue];
      
  • 將操作加入到隊(duì)列中
    • NSInvocationOperation
          NSInvocationOperation
       //    創(chuàng)建隊(duì)列
           NSOperationQueue * queue = [[NSOperationQueue alloc] init];
       //    封裝操作
           NSInvocationOperation  * op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(test) object:nil];
       //    把操作添加到隊(duì)列中
           [queue addOperation:op];
      
      
    • NSBlockOperation
      //    創(chuàng)建隊(duì)列
         NSOperationQueue * queue = [[NSOperationQueue alloc] init];
      //    封裝操作
         NSBlockOperation * bl = [NSBlockOperation blockOperationWithBlock:^{
         //code
      }];
           [bl addExecutionBlock:^{
             //code,追加操作滓窍,這一步卖词,可加可不加。
         }]
         //    把操作添加到隊(duì)列中
         [queue addOperation:bl];
      簡(jiǎn)寫方式
        [queue addOperationWithBlock:^{
             //code
         }];
      
  • 串行和并行
    • 這里有個(gè)關(guān)鍵參數(shù)maxConcurrentOperationCount,叫做最大并發(fā)數(shù)此蜈。
      • maxConcurrentOperationCount默認(rèn)情況下為-1即横,表示不進(jìn)行限制,默認(rèn)為并發(fā)執(zhí)行裆赵。
      • 當(dāng)maxConcurrentOperationCount為1時(shí)东囚,隊(duì)列為串行隊(duì)列。只能串行執(zhí)行战授。
      • 當(dāng)maxConcurrentOperationCount大于1時(shí)页藻,隊(duì)列為并發(fā)隊(duì)列。操作并發(fā)執(zhí)行植兰,當(dāng)然這個(gè)值不應(yīng)超過(guò)系統(tǒng)限制份帐,即使自己設(shè)置一個(gè)很大的值,系統(tǒng)也會(huì)自動(dòng)調(diào)整楣导。
      /**
       * 設(shè)置 MaxConcurrentOperationCount(最大并發(fā)操作數(shù))
       */
      - (void)setMaxConcurrentOperationCount {
      
          // 1.創(chuàng)建隊(duì)列
          NSOperationQueue *queue = [[NSOperationQueue alloc] init];
      
          // 2.設(shè)置最大并發(fā)操作數(shù)
          queue.maxConcurrentOperationCount = 1; // 串行隊(duì)列
      // queue.maxConcurrentOperationCount = 2; // 并發(fā)隊(duì)列
      // queue.maxConcurrentOperationCount = 8; // 并發(fā)隊(duì)列
      
          // 3.添加操作
          [queue addOperationWithBlock:^{
              for (int i = 0; i < 2; i++) {
                  [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
                  NSLog(@"1---%@", [NSThread currentThread]); // 打印當(dāng)前線程
              }
          }];
          [queue addOperationWithBlock:^{
              for (int i = 0; i < 2; i++) {
                  [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
                  NSLog(@"2---%@", [NSThread currentThread]); // 打印當(dāng)前線程
              }
          }];
          [queue addOperationWithBlock:^{
              for (int i = 0; i < 2; i++) {
                  [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
                  NSLog(@"3---%@", [NSThread currentThread]); // 打印當(dāng)前線程
              }
          }];
          [queue addOperationWithBlock:^{
              for (int i = 0; i < 2; i++) {
                  [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
                  NSLog(@"4---%@", [NSThread currentThread]); // 打印當(dāng)前線程
              }
          }];
      }
      
  • 線程依賴
    • NSOperation和NSOperationQueue最吸引人的地方是它能添加操作之間的依賴關(guān)系弥鹦。比如說(shuō)有A、B兩個(gè)操作爷辙,其中A執(zhí)行完操作,B才能執(zhí)行操作朦促,那么就需要讓B依賴于A膝晾。
    • -(void)addDependency:(NSOperation *)op; 添加依賴,使當(dāng)前操作依賴于操作 op 的完成务冕。
    • -(void)removeDependency:(NSOperation *)op; 移除依賴血当,取消當(dāng)前操作對(duì)操作 op 的依賴。
    • @property (readonly, copy) NSArray<NSOperation *> *dependencies; 在當(dāng)前操作開始執(zhí)行之前完成執(zhí)行的所有操作對(duì)象數(shù)組禀忆。
    /**
    * 操作依賴
    * 使用方法:addDependency:
    */
    - (void)addDependency {
    
       // 1.創(chuàng)建隊(duì)列
       NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
       // 2.創(chuàng)建操作
       NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
           for (int i = 0; i < 2; i++) {
               [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
               NSLog(@"1---%@", [NSThread currentThread]); // 打印當(dāng)前線程
           }
       }];
       NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
           for (int i = 0; i < 2; i++) {
               [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
               NSLog(@"2---%@", [NSThread currentThread]); // 打印當(dāng)前線程
           }
       }];
    
       // 3.添加依賴
       [op2 addDependency:op1]; // 讓op2 依賴于 op1臊旭,則先執(zhí)行op1,在執(zhí)行op2
    
       // 4.添加操作到隊(duì)列中
       [queue addOperation:op1];
       [queue addOperation:op2];
    }
    
  • 線程優(yōu)先級(jí)
    • NSOperation 提供了queuePriority(優(yōu)先級(jí))屬性箩退,queuePriority屬性適用于同一操作隊(duì)列中的操作离熏,不適用于不同操作隊(duì)列中的操作。默認(rèn)情況下戴涝,所有新創(chuàng)建的操作對(duì)象優(yōu)先級(jí)都是NSOperationQueuePriorityNormal滋戳。但是我們可以通過(guò)setQueuePriority:方法來(lái)改變當(dāng)前操作在同一隊(duì)列中的執(zhí)行優(yōu)先級(jí)。
       // 優(yōu)先級(jí)的取值
       typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
           NSOperationQueuePriorityVeryLow = -8L,
           NSOperationQueuePriorityLow = -4L,
           NSOperationQueuePriorityNormal = 0,
           NSOperationQueuePriorityHigh = 4,
           NSOperationQueuePriorityVeryHigh = 8
       };
      
    • 什么樣的操作才是進(jìn)入就緒狀態(tài)的操作呢啥刻?
      • 當(dāng)一個(gè)操作的所有依賴都已經(jīng)完成時(shí)奸鸯,操作對(duì)象通常會(huì)進(jìn)入準(zhǔn)備就緒狀態(tài),等待執(zhí)行可帽。
    • 舉個(gè)例子娄涩,現(xiàn)在有4個(gè)優(yōu)先級(jí)都是 NSOperationQueuePriorityNormal(默認(rèn)級(jí)別)的操作:op1,op2映跟,op3蓄拣,op4扬虚。其中 op3 依賴于 op2,op2 依賴于 op1弯蚜,即 op3 -> op2 -> op1】字幔現(xiàn)在將這4個(gè)操作添加到隊(duì)列中并發(fā)執(zhí)行。
      • 因?yàn)?op1 和 op4 都沒(méi)有需要依賴的操作碎捺,所以在 op1路鹰,op4 執(zhí)行之前,就是處于準(zhǔn)備就緒狀態(tài)的操作收厨。
      • 而 op3 和 op2 都有依賴的操作(op3 依賴于 op2晋柱,op2 依賴于 op1),所以 op3 和 op2 都不是準(zhǔn)備就緒狀態(tài)下的操作诵叁。
    • 理解了進(jìn)入就緒狀態(tài)的操作雁竞,那么我們就理解了queuePriority 屬性的作用對(duì)象
      • queuePriority 屬性決定了進(jìn)入準(zhǔn)備就緒狀態(tài)下的操作之間的開始執(zhí)行順序。并且拧额,優(yōu)先級(jí)不能取代依賴關(guān)系碑诉。
      • 如果一個(gè)隊(duì)列中既包含高優(yōu)先級(jí)操作,又包含低優(yōu)先級(jí)操作侥锦,并且兩個(gè)操作都已經(jīng)準(zhǔn)備就緒进栽,那么隊(duì)列先執(zhí)行高優(yōu)先級(jí)操作。比如上例中恭垦,如果 op1 和 op4 是不同優(yōu)先級(jí)的操作快毛,那么就會(huì)先執(zhí)行優(yōu)先級(jí)高的操作。
      • 如果番挺,一個(gè)隊(duì)列中既包含了準(zhǔn)備就緒狀態(tài)的操作唠帝,又包含了未準(zhǔn)備就緒的操作,未準(zhǔn)備就緒的操作優(yōu)先級(jí)比準(zhǔn)備就緒的操作優(yōu)先級(jí)高玄柏。那么襟衰,雖然準(zhǔn)備就緒的操作優(yōu)先級(jí)低,也會(huì)優(yōu)先執(zhí)行粪摘。優(yōu)先級(jí)不能取代依賴關(guān)系右蒲。如果要控制操作間的啟動(dòng)順序,則必須使用依賴關(guān)系赶熟。
  • 線程同步
    /**
    * 線程安全:使用 NSLock 加鎖
    * 初始化火車票數(shù)量瑰妄、賣票窗口(線程安全)、并開始賣票
    */
    
    - (void)initTicketStatusSave {
       NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當(dāng)前線程
    
       self.ticketSurplusCount = 50;
    
       self.lock = [[NSLock alloc] init];  // 初始化 NSLock 對(duì)象
    
       // 1.創(chuàng)建 queue1,queue1 代表北京火車票售賣窗口
       NSOperationQueue *queue1 = [[NSOperationQueue alloc] init];
       queue1.maxConcurrentOperationCount = 1;
    
       // 2.創(chuàng)建 queue2,queue2 代表上河匙火車票售賣窗口
       NSOperationQueue *queue2 = [[NSOperationQueue alloc] init];
       queue2.maxConcurrentOperationCount = 1;
    
       // 3.創(chuàng)建賣票操作 op1
       NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
           [self saleTicketSafe];
       }];
    
       // 4.創(chuàng)建賣票操作 op2
       NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
           [self saleTicketSafe];
       }];
    
       // 5.添加操作间坐,開始賣票
       [queue1 addOperation:op1];
       [queue2 addOperation:op2];
    }
    
    /**
    * 售賣火車票(線程安全)
    */
    - (void)saleTicketSafe {
       while (1) {
    
           // 加鎖
           [self.lock lock];
    
           if (self.ticketSurplusCount > 0) {
               //如果還有票,繼續(xù)售賣
               self.ticketSurplusCount--;
               NSLog(@"%@", [NSString stringWithFormat:@"剩余票數(shù):%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
               [NSThread sleepForTimeInterval:0.2];
           }
    
           // 解鎖
           [self.lock unlock];
    
           if (self.ticketSurplusCount <= 0) {
               NSLog(@"所有火車票均已售完");
               break;
           }
       }
    }
    
    
  • NSOperation 常用屬性和方法
    • 取消操作方法

      • -(void)cancel; 可取消操作,實(shí)質(zhì)是標(biāo)記 isCancelled 狀態(tài)竹宋。
    • 判斷操作狀態(tài)方法

      • -(BOOL)isFinished; 判斷操作是否已經(jīng)結(jié)束劳澄。
      • -(BOOL)isCancelled; 判斷操作是否已經(jīng)標(biāo)記為取消。
      • -(BOOL)isExecuting; 判斷操作是否正在在運(yùn)行蜈七。
      • -(BOOL)isReady; 判斷操作是否處于準(zhǔn)備就緒狀態(tài)秒拔,這個(gè)值和操作的依賴關(guān)系相關(guān)。
    • 操作同步

      • -(void)waitUntilFinished; 阻塞當(dāng)前線程飒硅,直到該操作結(jié)束砂缩。可用于線程執(zhí)行順序的同步三娩。
      • -(void)setCompletionBlock:(void (^)(void))block; completionBlock 會(huì)在當(dāng)前操作執(zhí)行完畢時(shí)執(zhí)行 completionBlock庵芭。
      • -(void)addDependency:(NSOperation *)op; 添加依賴,使當(dāng)前操作依賴于操作 op 的完成雀监。
      • -(void)removeDependency:(NSOperation *)op; 移除依賴双吆,取消當(dāng)前操作對(duì)操作 op 的依賴。
      • @property (readonly, copy) NSArray<NSOperation *> *dependencies; 在當(dāng)前操作開始執(zhí)行之前完成執(zhí)行的所有操作對(duì)象數(shù)組会前。
  • NSOperationQueue 常用屬性和方法
    • 取消/暫停/恢復(fù)操作
      • -(void)cancelAllOperations; 可以取消隊(duì)列的所有操作好乐。
      • -(BOOL)isSuspended; 判斷隊(duì)列是否處于暫停狀態(tài)。 YES 為暫停狀態(tài)瓦宜,NO 為恢復(fù)狀態(tài)曹宴。
      • -(void)setSuspended:(BOOL)b; 可設(shè)置操作的暫停和恢復(fù),YES 代表暫停隊(duì)列歉提,NO 代表恢復(fù)隊(duì)列。
    • 操作同步
      • -(void)waitUntilAllOperationsAreFinished; 阻塞當(dāng)前線程区转,直到隊(duì)列中的操作全部執(zhí)行完畢苔巨。
    • 添加/獲取操作`
      • -(void)addOperationWithBlock:(void (^)(void))block; 向隊(duì)列中添加一個(gè) NSBlockOperation 類型操作對(duì)象。
      • -(void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait; 向隊(duì)列中添加操作數(shù)組废离,wait 標(biāo)志是否阻塞當(dāng)前線程直到所有操作結(jié)束
      • -(NSArray *)operations; 當(dāng)前在隊(duì)列中的操作數(shù)組(某個(gè)操作執(zhí)行結(jié)束后會(huì)自動(dòng)從這個(gè)數(shù)組清除)侄泽。
      • -(NSUInteger)operationCount; 當(dāng)前隊(duì)列中的操作數(shù)庸毫。
    • 獲取隊(duì)列
      • +(id)currentQueue; 獲取當(dāng)前隊(duì)列沟于,如果當(dāng)前線程不是在 NSOperationQueue 上運(yùn)行則返回 nil梳码。
      • +(id)mainQueue; 獲取主隊(duì)列予颤。
  • 注意:
    • 1.這里的暫停和取消(包括操作的取消和隊(duì)列的取消)并不代表可以將當(dāng)前的操作立即取消膘流,而是當(dāng)當(dāng)前的操作執(zhí)行完畢之后不再執(zhí)行新的操作冰悠。
    • 2.暫停和取消的區(qū)別就在于:暫停操作之后還可以恢復(fù)操作鸟缕,繼續(xù)向下執(zhí)行鸭限;而取消操作之后俯画,所有的操作就清空了析桥,無(wú)法再接著執(zhí)行剩下的操作。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市泡仗,隨后出現(xiàn)的幾起案子埋虹,更是在濱河造成了極大的恐慌,老刑警劉巖娩怎,帶你破解...
    沈念sama閱讀 212,718評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件搔课,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡截亦,警方通過(guò)查閱死者的電腦和手機(jī)爬泥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)魁巩,“玉大人急灭,你說(shuō)我怎么就攤上這事」人欤” “怎么了葬馋?”我有些...
    開封第一講書人閱讀 158,207評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)肾扰。 經(jīng)常有香客問(wèn)我畴嘶,道長(zhǎng),這世上最難降的妖魔是什么集晚? 我笑而不...
    開封第一講書人閱讀 56,755評(píng)論 1 284
  • 正文 為了忘掉前任窗悯,我火速辦了婚禮,結(jié)果婚禮上偷拔,老公的妹妹穿的比我還像新娘蒋院。我一直安慰自己,他們只是感情好莲绰,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評(píng)論 6 386
  • 文/花漫 我一把揭開白布欺旧。 她就那樣靜靜地躺著,像睡著了一般蛤签。 火紅的嫁衣襯著肌膚如雪辞友。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,050評(píng)論 1 291
  • 那天震肮,我揣著相機(jī)與錄音称龙,去河邊找鬼。 笑死戳晌,一個(gè)胖子當(dāng)著我的面吹牛鲫尊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播沦偎,決...
    沈念sama閱讀 39,136評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼马昨,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼竞帽!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起鸿捧,我...
    開封第一講書人閱讀 37,882評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤屹篓,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后匙奴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體堆巧,經(jīng)...
    沈念sama閱讀 44,330評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評(píng)論 2 327
  • 正文 我和宋清朗相戀三年泼菌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了谍肤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,789評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡哗伯,死狀恐怖荒揣,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情焊刹,我是刑警寧澤系任,帶...
    沈念sama閱讀 34,477評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站虐块,受9級(jí)特大地震影響俩滥,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜贺奠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評(píng)論 3 317
  • 文/蒙蒙 一霜旧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧儡率,春花似錦挂据、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至箕肃,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間今魔,已是汗流浹背勺像。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留错森,地道東北人吟宦。 一個(gè)月前我還...
    沈念sama閱讀 46,598評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像涩维,于是被迫代替她去往敵國(guó)和親殃姓。 傳聞我的和親對(duì)象是個(gè)殘疾皇子袁波,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評(píng)論 2 351