iOS 面試第九節 多線程

在這裏插入圖片描述
補充:
同步、全局對列 ABC 未開啓子線程 任務同步有序
異步、全局對列 ABC 新開多個子線程 任務亂序執行(任務少有序,太多時也有可能會出現任務執行亂序。。。)

1.進程與線程

  • 進程
    1.進程是一個具有一定獨立功能的程序關於某次數據集合的一次運行活動,它是操作系統分配資源的基本單元.
    2.進程是指在系統中正在運行的一個應用程序,就是一段程序的執行過程,我們可以理解爲手機上的一個app.
    3.每個進程之間是獨立的,每個進程均運行在其專用且受保護的內存空間內,擁有獨立運行所需的全部資源

  • 線程
    1.程序執行流的最小單元,線程是進程中的一個實體.
    2.一個進程要想執行任務,必須至少有一條線程.應用程序啓動的時候,系統會默認開啓一條線程,也就是主線程

  • 進程和線程的關係
    1.線程是進程的執行單元,進程的所有任務都在線程中執行
    2.線程是 CPU 分配資源和調度的最小單位
    3.一個進程中可有多個線程,但至少要有一條線程
    4.同一個進程內的線程共享進程資源

2.什麼是多線程?

  • 多線程的實現原理:事實上,同一時間內單核的CPU只能執行一個線程,多線程是CPU快速的在多個線程之間進行切換(調度),造成了多個線程同時執行的假象。
  • 如果是多核CPU就真的可以同時處理多個線程了。
  • 多線程的目的是爲了同步完成多項任務,通過提高系統的資源利用率來提高系統的效率。

3.多線程的優點和缺點

  • 優點
    能適當提高程序的執行效率
    能適當提高資源利用率(CPU、內存利用率)
  • 缺點:
  • 多線程編程實際上是一種易發生各種問題的編程技術。比如多個線程更新相同資源會導致數據的不一致(數據競爭)、停止等待事件的線程會導致多個線程相互等待(死鎖)、使用太多線程會消耗大量內存等
    開啓線程需要佔用一定的內存空間(默認情況下,主線程佔用1M,子線程佔用512KB),如果開啓大量的線程,會佔用大量的內存空間,降低程序的性能
    線程越多,CPU在調度線程上的開銷就越大
    程序設計更加複雜:比如線程之間的通信、多線程的數據共享
    在這裏插入圖片描述

4.同步(Synchronous) 和 異步(Asynchronous)

同步和異步描述的其實就是函數什麼時候返回. 比如用來下載圖片的函數A: {download image}, 同步函數只有在image下載結束之後才返回, 下載的這段時間函數A只能搬個小板凳在那兒坐等… 而異步函數, 立即返回. 圖片會去下載, 但函數A不會去等它完成. So, 異步函數不會堵塞當前線程去執行下一個函數!

5.串行(Serial)和 並行(Parallelism)

串行和並行描述的是任務和任務之間的執行方式. 串行是任務A執行完了任務B才能執行, 它們倆只能順序執行. 並行則是任務A和任務B可以同時執行.

6.並行(Parallelism) 和 併發(Concurrency) 有什麼區別?

並行就是同一時間做多個事
併發就是一個時間段內做多個事情。
所以並行的話,是需要多核cpu多個線程同時執行,如果單核cpu就只有併發了。

  • 並行:充分利用計算機的多核,在多個線程上同步進行
  • 併發:在一條線程上通過快速切換,讓人感覺在同步進行

7.線程從開始到結束的狀態

  • 新建狀態

    • 通過上面3中方法實 例化線程對象
    • 程序還沒有開始運行線程中的代碼
  • 就緒狀態
    oc的NSThread對於的幾種開線程方式

    • 向線程對象發送 start 消息,線程對象被加入 可調度線程池 等待 CPU 調度
    • detachNewThreadSelector 方法
      detachNewThreadWithBlock和
      performSelectorInBackground 方法
      會直接實例化一個線程對象並加入 可調度線程池
    • 處於就緒狀態的線程並不一定立即執行線程裏的代碼,線程還必須同其他線程競爭CPU時間,只有獲得CPU時間纔可以運行線程。
  • 運行狀態

    • CPU 負責調度可調度線程池中線程的執行
    • 線程執行完成之前(死亡之前),狀態可能會在就緒和運行之間來回切換
    • 就緒和運行之間的狀態變化由 CPU 負責,程序員不能干預
  • 阻塞狀態

    • 所謂阻塞狀態是正在運行的線程沒有運行結束,暫時讓出CPU,這時其他處於就緒狀態的線程就可以獲得CPU時間,進入運行狀態。
    • 線程通過調用sleep方法進入睡眠狀態
    • 線程調用一個在I/O上被阻塞的操作,即該操作在輸入輸出操作完成之前不會返回到它的調用者
    • 線程試圖得到一個鎖,而該鎖正被其他線程持有;
    • 線程在等待某個觸發條件
  + (void)sleepUntilDate:(NSDate *)date;//休眠到指定日期
  + (void)sleepForTimeInterval:(NSTimeInterval)ti;//休眠指定時長
  @synchronized(self):互斥鎖
  sleep(unsigned int) __DARWIN_ALIAS_C(sleep);
  • 死亡狀態
    • 正常死亡
      • 線程執行完畢
    • 非正常死亡
      • 當滿足某個條件後,在線程內部自己中止執行(自殺)
      • 當滿足某個條件後,在主線程給其它線程打個死亡標記(下聖旨),讓子線程自行了斷.(被逼着死亡)

在這裏插入圖片描述
上面就是線程生命週期
當線程處於就緒狀態時線程會被移到可調度線程池裏面(CPU只調度此線程池裏面的線程),當處於阻塞狀態時,線程會被移出可調度線程池,當處於死亡狀態時 先移出線程池,再從內存中釋放。

線程通信
線程在運行過程中,可能需要與其它線程進行通信,如在主線程中修改界面等等,可以使用如下接口:

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait

線程的同步與鎖
關於鎖這一塊,需要單獨列一塊,上鎖方式很多種,性能較優的有 dispatch_semaphore_t (信號量上鎖)

線程的同步與鎖什麼時候會遇到?就是我們在線程公用資源的時候,導致的資源競爭。舉個例子:多個窗口同時售票的售票系統!

- (void)viewDidLoad {
    [super viewDidLoad];
    
    tickets = 100;
    count = 0;
    
    //鎖對象
    self.ticketsLock = [[NSLock alloc] init];

    self.ticketsThreadOne = [[NSThread alloc] initWithTarget:self selector:@selector(sellAction) object:nil];
    self.ticketsThreadOne.name = @"thread-1";
    [self.ticketsThreadOne start];
    
    self.ticketsThreadTwo = [[NSThread alloc] initWithTarget:self selector:@selector(sellAction) object:nil];
    self.ticketsThreadTwo.name = @"thread-2";
    [self.ticketsThreadTwo start];
    
}

- (void)sellAction{
    while (true) {
        //上鎖
        [self.ticketsLock lock];
        if (tickets > 0) {
            [NSThread sleepForTimeInterval:0.5];
            count = 100 - tickets;
            NSLog(@"當前總票數是:%ld----->賣出:%ld----->線程名:%@",tickets,count,[NSThread currentThread]);
            tickets--;
        }else{
            break;
        }
        //解鎖
        [self.ticketsLock unlock];
    }
}

@end

通過上面的Demo應該理解線程的同步以及鎖的使用問題

[myLock lock]

資源處理....

[myLock unLock];

8.iOS中實現多線程的幾種方案,各自有什麼特點?

  • NSThread 面向對象的,需要程序員手動創建線程,但不需要手動銷燬。子線程間通信很難。
- (void)testThread {
    
//    [NSThread detachNewThreadSelector:<#(nonnull SEL)#> toTarget:<#(nonnull id)#> withObject:<#(nullable id)#>];
    NSLog(@"A:%@",[NSThread currentThread]);
    
    //異步、同步 的區別就是函數什麼時候返回,異步直接返回不影響繼續執行下面代碼,同步是等待子線程執行結束再返回然後再執行下面代碼
    
    //NSThread 的對象方法開線程  異步 ACB
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadDownloadImage) object:nil];
    [thread start];// 需要手動開啓
    
    //使用NSThread 類方法開啓子線程 異步
//    [NSThread detachNewThreadWithBlock:^{
//        [self threadDownloadImage];
//    }];
    
    //使用NSThread 類方法開子線程 異步
//    [NSThread detachNewThreadSelector:@selector(threadDownloadImage) toTarget:self withObject:nil];
    
    //使用NSObject的分類方式開子線程 異步
//    [self performSelectorInBackground:@selector(threadDownloadImage) withObject:nil];
    
    //使用NSObject的分類方式開子線程 同步,也就是打印 ABC
//    [self performSelector:@selector(threadDownloadImage)];
    
    NSLog(@"C:%@",[NSThread currentThread]);
}

- (void)threadDownloadImage {
    sleep(5);
    NSLog(@"B:%@",[NSThread currentThread]);
    NSLog(@"下載大量圖片數據");
}
  • GCD c語言,充分利用了設備的多核,自動管理線程生命週期。比NSOperation效率更高。

    GCD(Grand Central Dispatch)是異步執行任務的技術之一。一般將應用程序中記述的線程管理用的代碼在系統級中實現。開發者只需要定義想執行的任務並追加到適當的Dispatch Queue中,GCD就能生成必要的線程並計劃執行任務。

任務和隊列

  • 任務:就是執行操作的意思,換句話說就是你在線程中執行的那段代碼。在GCD中是放在block中的。執行任務有兩種方式:同步執行和異步執行。兩者的主要區別是:是否具備開啓新線程的能力。

    • 同步執行(sync):只能在當前線程中執行任務,不具備開啓新線程的能力
      • 必須等待當前語句執行完畢,纔會執行下一條語句
      • 不會開啓線程
      • 在當前主線程執行 block 的任務
      • dispatch_sync(queue, block);
    • 異步執行(async):可以在新的線程中執行任務,具備開啓新線程的能力
      • 不用等待當前語句執行完畢,就可以執行下一條語句
      • 會開啓線程執行 block 的任務
      • 異步是多線程的代名詞
      • dispatch_async(queue, block);
  • 隊列:這裏的隊列指任務隊列,即用來存放任務的隊列。隊列是一種特殊的線性表,採用FIFO(先進先出)的原則,即新任務總是被插入到隊列的末尾,而讀取任務的時候總是從隊列的頭部開始讀取。每讀取一個任務,則從隊列中釋放一個任務。在GCD中有四種隊列:串行隊列、併發隊列、主隊列、全局隊列。

    1. 串行隊列(Serial Dispatch Queue):讓任務一個接着一個地執行(一個任務執行完畢後,再執行下一個任務)

      • 一次只能"調度"一個任務
      • dispatch_queue_create("queue", NULL);或者或者dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
    2. 併發隊列(Concurrent Dispatch Queue):可以讓多個任務併發(同時)執行(自動開啓多個線程同時執行任務),

      • 一次可以"調度"多個任務
      • 併發功能只有在異步(dispatch_async)函數下才有效
      • dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    3. 主隊列:

      • 專門用來在主線程上調度任務的隊列
      • 不會開啓線程
      • 在主線程空閒時纔會調度隊列中的任務在主線程執行
      • dispatch_get_main_queue();
    4. 全局隊列:

      • 執行過程和併發隊列一致,參考併發隊列
      • dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

小結: 在以後的使用中,記住下面的就可以了!

1.開不開線程由執行任務的函數決定

  • 異步開,異步是多線程的代名詞
  • 同步不開

2.開幾條線程由隊列決定

  • 串行隊列開一條線程(GCD會開一條,NSOperation Queue最大併發數爲1時也可能開多條)
  • 併發隊列開多條線程,具體能開的線程數量由底層線程池決定

下面代碼實踐

//前提 testThread 主線程調用的
- (void)testThread {
    NSLog(@"A:%@",[NSThread currentThread]);
//    [self threadMainUse]; //主線程開啓的
    [self gcdMainUse]; //主線程中開啓的
    NSLog(@"C:%@",[NSThread currentThread]);
}

//模擬網絡下載
- (void)threadDownloadImage {
    sleep(5);
    NSLog(@"B 下載大量圖片數據:%@",[NSThread currentThread]);
}

//GCD 試驗田
- (void)gcdMainUse {
    //再次重申 異步 同步 跟開不開線程沒有關係,只是讓你當前代碼要不要立刻跳過去往下繼續執行
    
    
    //串行隊列之探索  SERIAL
//    dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
//    dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", NULL);

    //同步 串行
    // 同步串行 ABC 未開子線程  任務有序執行 for模擬downImage多任務是否順序執行(按次序執行)
//    for (int i = 0; i < 6; i++) {
//        dispatch_sync(serialQueue, ^{
//            NSLog(@"b_%d",i);
//            [self threadDownloadImage];
//        });
//    }

    
    // 異步 串行
    // 異步、串行  ACB  新開多個子線程  任務亂序執行(任務少有序,太多時也有可能會出現任務執行亂序。。。)
//    for (int i = 0; i < 6; i++) {
//        dispatch_async(serialQueue, ^{
//            NSLog(@"b_%d",i);
//            [self threadDownloadImage];
//        });
//    }

    
    
    //併發隊列之探索  Concurrent
//    dispatch_queue_t concurrentQueue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    
    // 同步、併發  ABC  未開子線程  任務有序執行
//    for (int i = 0; i < 6; i++) {
//        dispatch_sync(concurrentQueue, ^{
//            NSLog(@"b_%d",i);
//            [self threadDownloadImage];
//        });
//    }
    
    //異步、併發  ACB b_0 b_2 b_5 b_4   新開多個子線程 任務隨機執行 (任務是沒有順序隨機執行)
//    for (int i = 0; i < 6; i++) {
//        dispatch_async(concurrentQueue, ^{
//            NSLog(@"b_%d",i);
//            [self threadDownloadImage];
//        });
//    }
    
    
    //全局隊列之探索  global_queue
//    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    //同步、全局對列 ABC 未開啓子線程 任務同步有序
//    for (int i = 0; i < 6; i++) {
//        dispatch_sync(globalQueue, ^{
//            NSLog(@"b_%d",i);
//            [self threadDownloadImage];
//        });
//    }
    
    //異步、全局對列 ABC 新開多個子線程 任務亂序執行(任務少有序,太多時也有可能會出現任務執行亂序。。。)
//    for (int i = 0; i < 6; i++) {
//        dispatch_async(globalQueue, ^{
//            NSLog(@"b_%d",i);
//            [self threadDownloadImage];
//        });
//    }
    
    //主隊列隊列之探索  main_queue
//    dispatch_queue_t mainQueue = dispatch_get_main_queue();
  
    //同步主隊列 死鎖 (原因:源代碼在Main Dispatch Queue 即主隊列中執行指定的block任務,並等待其結束。而其實在主線程中正在執行這些源代碼,所以無法執行追加到Main Dispatch Queue 的block任務。 解釋一下就是:gcd代碼同步執行, 會阻塞當前線程, 也就是任務B執行完成以後纔會執行任務C. 主隊列是串行隊列, 當一個任務執行完成以後纔會執行另一個任務, 也就是任務C執行完成以後纔會執行任務B. 所以任務B和任務C就會一直相互等待, 形成死鎖.)
//    for (int i = 0; i < 6; i++) {
//        dispatch_sync(mainQueue, ^{
//            NSLog(@"b_%d",i);
//            [self threadDownloadImage];
//        });
//    }
    
    //異步主隊列 ACB 未開啓子線程  任務有序執行
//    for (int i = 0; i < 6; i++) {
//        dispatch_async(mainQueue, ^{
//            NSLog(@"b_%d",i);
//            [self threadDownloadImage];
//        });
//    }

    // 延遲函數
    //  延時函數並非是在指定的時間後開始執行任務block, 而是在指定的時間後將任務block異步添加到指定的隊列中, 然後再等待從隊列中取出任務block放入線程中去執行, 如果block分配的線程被sleep了一段時間, 那麼這個延遲函數就不太準確了, 所以延遲函數dispatch_after存在一定的時間誤差.

//    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC));
//    dispatch_after(time, dispatch_get_main_queue(), ^{
//        NSLog(@"B");
//    });


    
    // 柵欄函數。哈哈哈哈哈 顧名思義,就是個柵欄隔離 ,比如任務ABC執行完後執行F
    //執行順序 ACB(F前面的執行順序)FD
    //白話翻譯:當柵欄函數中的任務block到達併發隊列queue的最前邊時, 任務block並不會立即執行, 它會等待併發隊列中正在執行的任務執行完成以後才執行柵欄函數的任務. 任何在柵欄函數以後添加到併發隊列中的任務都會等待柵欄函數中的任務執行完成以後才執行.

//    dispatch_queue_t conQueue = dispatch_queue_create("conQueue", DISPATCH_QUEUE_CONCURRENT);
//    
//    dispatch_async(conQueue, ^{
//        NSLog(@"任務A");
//    });
//    
//    dispatch_async(conQueue, ^{
//        NSLog(@"任務B");
//    });
//    
//    dispatch_async(conQueue, ^{
//        NSLog(@"任務C");
//    });
//    
//    dispatch_barrier_async(conQueue, ^{
//        NSLog(@"任務F");
//    });
//    
//    dispatch_async(conQueue, ^{
//        NSLog(@"任務D");
//    });

    //隊列組。 任務場景在n個耗時併發任務都完成後,再去執行接下來的任務。比如,在n個網絡請求完成後去刷新UI頁面。
    //流程是 先創建組,把隊列加入組中,組通知任務完成後回主線程刷新
//    dispatch_queue_t concurrentQueue = dispatch_queue_create("test1", DISPATCH_QUEUE_CONCURRENT);
//    dispatch_group_t group = dispatch_group_create();
//
//    for (NSInteger i = 0; i < 10; i++) {
//        dispatch_group_async(group, concurrentQueue, ^{
//            sleep(1);
//            NSLog(@"%zd:網絡請求",i);
//        });
//    }
//
//    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//        NSLog(@"刷新頁面");
//    });
}

同步串行,如果同步線程與串行執行方法線程在同一個線程會造成死鎖。
比如在主線程,進行同步主隊列會死鎖。
在子線程,進行同步串行會死鎖。

  • NSOperation 基於gcd封裝,更加面向對象,比gcd多了一些功能。
- (void)operationThreadUse {
    //operation 開啓線程同 NSThread相似,也是三種方式
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"任務B1-%@",[NSThread currentThread]);
        }
    }];

    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"任務B2-%@",[NSThread currentThread]);
        }
    }];
    
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"任務3-%@",[NSThread currentThread]);
        }
    }];
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//    //設置任務依賴,意思是任務2在任務3後面執行,所以也就是任務B1 B3無序先執行
//    [operation2 addDependency:operation3];
//    [queue addOperation:operation1];
//    [queue addOperation:operation2];
//    [queue addOperation:operation3];

    //線程間通訊
    [queue addOperationWithBlock:^{
        NSLog(@"在子線程中執行--任務1--%@", [NSThread currentThread]);
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                NSLog(@"在主線程中執行--任務2--%@", [NSThread currentThread]);
        }];
    }];
    
//    NSInvocationOperation *operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operation1Selector) object:nil];
//    NSInvocationOperation *operation2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operation2Selector) object:nil];

        // 取消某一個操作
    //    [operation1 cancel];
        
        // 暫停隊列
//        [queue setSuspended:YES];

        // 取消隊列中的操作
    //    [queue cancelAllOperations];
//        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//            [queue setSuspended:NO];
//        });
        
//        [queue addOperation:operation1];
//        [queue addOperation:operation2];

    
    
    //A B1 B2 C 新開子線程產生 有序執行
//    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//    queue.maxConcurrentOperationCount = 1;
//    [queue addOperation:operation1];
//    [queue addOperation:operation2];
    
    
    //A B1 B2 C 新開子線程產生 有序執行
//    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//    [queue addOperationWithBlock:^{
//        for (int i = 0; i < 3; i++) {
//            NSLog(@"任務1-%@",[NSThread currentThread]);
//        }
//    }];
//
//    [queue addOperationWithBlock:^{
//        for (int i = 0; i < 3; i++) {
//            NSLog(@"任務2-%@",[NSThread currentThread]);
//        }
//    }];
}

iOS多線程方案
iOS開發之多線程編程總結

9.多個網絡請求完成後一起執行下一步

  • 使用GCD的dispatch_group_t

創建一個dispatch_group_t
每次網絡請求前先dispatch_group_enter,請求回調後再dispatch_group_leave,enter和leave必須配合使用,有幾次enter就要有幾次leave,否則group會一直存在。
當所有enter的block都leave後,會執行dispatch_group_notify的block。

NSString *str = @"http://xxxx.com/";
NSURL *url = [NSURL URLWithString:str];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];

dispatch_group_t downloadGroup = dispatch_group_create();
for (int i=0; i<10; i++) {
    dispatch_group_enter(downloadGroup);
    
    NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        NSLog(@"%d---%d",i,i);
        dispatch_group_leave(downloadGroup);
    }];
    [task resume];
}

dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{
    NSLog(@"end");
});
  • 使用GCD的信號量dispatch_semaphore_t
    dispatch_semaphore信號量爲基於計數器的一種多線程同步機制。如果semaphore計數大於等於1,計數-1,返回,程序繼續運行。如果計數爲0,則等待。dispatch_semaphore_signal(semaphore)爲計數+1操作,dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER)爲設置等待時間,這裏設置的等待時間是一直等待。

創建semaphore爲0,等待,等10個網絡請求都完成了,dispatch_semaphore_signal(semaphore)爲計數+1,然後計數-1返回

NSString *str = @"http://xxxx.com/";
NSURL *url = [NSURL URLWithString:str];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];

dispatch_semaphore_t sem = dispatch_semaphore_create(0);
for (int i=0; i<10; i++) {
    
    NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        NSLog(@"%d---%d",i,i);
        count++;
        if (count==10) {
            dispatch_semaphore_signal(sem);
            count = 0;
        }
    }];
    [task resume];
}
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);

dispatch_async(dispatch_get_main_queue(), ^{
    NSLog(@"end");
});

10.多個網絡請求順序執行後執行下一步

  • 使用信號量semaphore
    每一次遍歷,都讓其dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER),這個時候線程會等待,阻塞當前線程,直到dispatch_semaphore_signal(sem)調用之後
NSString *str = @"http://www.jianshu.com/p/6930f335adba";
NSURL *url = [NSURL URLWithString:str];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];

dispatch_semaphore_t sem = dispatch_semaphore_create(0);
for (int i=0; i<10; i++) {
    
    NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        
        NSLog(@"%d---%d",i,i);
        dispatch_semaphore_signal(sem);
    }];
    
    [task resume];
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
}

dispatch_async(dispatch_get_main_queue(), ^{
    NSLog(@"end");
});

11.異步操作兩組數據時, 執行完第一組之後, 才能執行第二組

  • 這裏使用dispatch_barrier_async柵欄方法即可實現
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{
    NSLog(@"第一次任務的主線程爲: %@", [NSThread currentThread]);
});

dispatch_async(queue, ^{
    NSLog(@"第二次任務的主線程爲: %@", [NSThread currentThread]);
});

dispatch_barrier_async(queue, ^{
    NSLog(@"第一次任務, 第二次任務執行完畢, 繼續執行");
});

dispatch_async(queue, ^{
    NSLog(@"第三次任務的主線程爲: %@", [NSThread currentThread]);
});

dispatch_async(queue, ^{
    NSLog(@"第四次任務的主線程爲: %@", [NSThread currentThread]);
});
  • 通過隊列組 也可以(個人認爲)
    -將之前任務1任務2放到group1,任務3任務4放到一組。group1執行完成後執行group2

12.多線程中的死鎖?

死鎖是由於多個線程(進程)在執行過程中,因爲爭奪資源而造成的互相等待現象,你可以理解爲卡主了。產生死鎖的必要條件有四個:

互斥條件 : 指進程對所分配到的資源進行排它性使用,即在一段時間內某資源只由一個進程佔用。如果此時還有其它進程請求資源,則請求者只能等待,直至佔有資源的進程用畢釋放。

請求和保持條件 : 指進程已經保持至少一個資源,但又提出了新的資源請求,而該資源已被其它進程佔有,此時請求進程阻塞,但又對自己已獲得的其它資源保持不放。

不可剝奪條件 : 指進程已獲得的資源,在未使用完之前,不能被剝奪,只能在使用完時由自己釋放。

環路等待條件 : 指在發生死鎖時,必然存在一個進程——資源的環形鏈,即進程集合{P0,P1,P2,···,Pn}中的P0正在等待一個P1佔用的資源;P1正在等待P2佔用的資源,……,Pn正在等待已被P0佔用的資源。

最常見的就是 同步函數 + 主隊列 的組合,本質是隊列阻塞。

dispatch_sync(dispatch_get_main_queue(), ^{
    NSLog(@"2");
});
NSLog(@"1");
// 什麼也不會打印,直接報錯

13.GCD執行原理?

  • GCD有一個底層線程池,這個池中存放的是一個個的線程。之所以稱爲“池”,很容易理解出這個“池”中的線程是可以重用的,當一段時間後這個線程沒有被調用胡話,這個線程就會被銷燬。注意:開多少條線程是由底層線程池決定的(線程建議控制再3~5條),池是系統自動來維護,不需要我們程序員來維護(看到這句話是不是很開心?)
    而我們程序員需要關心的是什麼呢?我們只關心的是向隊列中添加任務,隊列調度即可。

  • 如果隊列中存放的是同步任務,則任務出隊後,底層線程池中會提供一條線程供這個任務執行,任務執行完畢後這條線程再回到線程池。這樣隊列中的任務反覆調度,因爲是同步的,所以當我們用currentThread打印的時候,就是同一條線程。

  • 如果隊列中存放的是異步的任務,(注意異步可以開線程),當任務出隊後,底層線程池會提供一個線程供任務執行,因爲是異步執行,隊列中的任務不需等待當前任務執行完畢就可以調度下一個任務,這時底層線程池中會再次提供一個線程供第二個任務執行,執行完畢後再回到底層線程池中。

  • 這樣就對線程完成一個複用,而不需要每一個任務執行都開啓新的線程,也就從而節約的系統的開銷,提高了效率。在iOS7.0的時候,使用GCD系統通常只能開58條線程,iOS8.0以後,系統可以開啓很多條線程,但是實在開發應用中,建議開啓線程條數:35條最爲合理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章