iOS多線程之 GCD

GCD的優勢

GCD是蘋果公司爲多核的並行運算提出的解決方案
GCD會自動利用更多的CPU內核(比如雙核、四核)
GCD會自動管理線程的生命週期(創建線程、調度任務、銷燬線程)
程序員只需要告訴GCD想要執行什麼任務,不需要編寫任何線程管理代碼(但是更優的做法是我們自己管理線程,這個後面會說到)

GCD的兩個核心

  • 任務:執行什麼操作
  • 隊列:用來存放任務

GCD使用的兩個步驟

  • 創建任務:確定要做的事情
  • 將任務添加到隊列中
    • GCD會自動將隊列中的任務取出,放到對應的線程中執行
    • 任務的取出遵循隊列的FIFO(First In First Out)原則:
      先進先出,後進後出

執行任務的方式

GCD中有2個用來執行任務的函數

  • 同步的方式執行任務
//queue:隊列
//block:任務
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
  • 異步的方式執行任務
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

隊列的類型

GCD的隊列可以分爲2大類型

  • 併發隊列Concurrent Dispatch Queue
    • 可以讓多個任務併發(同時)執行(自動開啓多個線程同時執行任務)
    • 併發功能只有在異步(dispatch_async)函數下才有效
  • 串行隊列Serial Dispatch Queue
    • 讓任務一個接着一個地執行(一個任務執行完畢後,再執行下一個任務)

小結

同步和異步決定了要不要開啓新的線程

  • 同步:在當前線程中執行任務,不具備開啓新線程的能力
  • 異步:在新的線程中執行任務,具備開啓新線程的能力

併發和串行決定了任務的執行方式

  • 併發:多個任務併發(同時)執行
  • 串行:一個任務執行完畢後,再執行下一個任務

簡單用法

串行隊列,同步執行

  • 不會新建線程,按順序執行任務(毫無用處) 補充:在GCDAsyncSocket 有這種用法 用於切換當前任務的執行queue

    GCDAsyncSocket

#define DISPATCH_QUEUE_SERIAL NULL
//串行隊列
//dispatch_queue_t q = dispatch_queue_create("test", NULL);
dispatch_queue_t q = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i < 10; i++) 
{
    //同步執行
    dispatch_sync(q, ^{
       NSLog(@"%@ -- %d",[NSThread currentThread],i);
    });
}

串行隊列,異步執行

  • 會新建線程,按順序執行任務(非常有用)
//只有一個線程,因爲是串行隊列,只有一個線程就可以按順序執行隊列中的所有任務
dispatch_queue_t q = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
    for (int i = 0; i < 10; i++) 
    {
        //異步執行
        dispatch_async(q, ^{
            NSLog(@"%@ -- %d",[NSThread currentThread],i);
        });
    }

並行隊列,異步執行

  • 會新建多個線程,但是無法確定任務的執行順序(有用,但是很容易出錯)
dispatch_queue_t q = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i < 10; i++) 
    {
        //異步執行
        dispatch_async(q, ^{
            NSLog(@"%@ -- %d",[NSThread currentThread],i);
        });
    }

並行隊列,同步執行

  • 不會新建線程,按順序執行任務(幾乎沒用)
    dispatch_queue_t q = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i < 10; i++)
    {
        //異步執行
        dispatch_sync(q, ^{
            NSLog(@"%@ -- %d",[NSThread currentThread],i);
        });
    }

一些系統提供的隊列

全局隊列

  • 全局隊列本質就是併發隊列
dispatch_get_global_queue(0,0);
  • 全局隊列和併發隊列的區別
  • 併發隊列有名稱,可以跟蹤錯誤,全局隊列沒有
  • 在ARC中不需要考慮釋放內存, dispatch_release(q);不允許調用。在MRC中需要手動釋放內存,併發隊列是create創建出來的 在MRC中見到create就要release,全局隊列不需要release(只有一個)
  • 一般我們使用全局隊列,但是建議同一類事情創建一個固定的隊列進行管理,比如編寫SDK我們不能使得我們的SDK導入的時候影響到別人隊列裏面的操作,導致別人莫名其妙導入你的SDK之後出現了卡頓,因此建議編寫網絡框架、處理繪圖、編寫其他輪子代碼的時候創建一個自己專屬的隊列。

主隊列

  • 主隊列,異步任務
    • 不開線程,同步執行
    • 主隊列特點:如果主線程正在執行代碼暫時不調度任務,等主線程執行結束後在執行任務
    • 主隊列又叫 全局串行隊列
  • 主隊列,同步執行
  • 程序執行不出來(死鎖)
  • 死鎖的原因,當程序執行到下面這段代碼的時候
    主隊列:如果主線程正在執行代碼,就不調度任務
    同步執行:如果第一個任務沒有執行,就繼續等待第一個任務執行完成,再執行下一個任務此時互相等待,程序無法往下執行(死鎖)
//主隊列的特點:主隊列只有當主線程空閒下來的時候才能夠執行主隊列裏面的任務
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"哈哈");
    });


GCD 進階

一次執行 dispatch_once

該函數的作用是保證block在程序的生命週期範圍內只執行一次。最常用最常用的場景就是創建單例了

  • 單例模式
+ (instancetype)sharedManager {
    static id instance;
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });
    
    return instance;
}

延遲執行 Dispatch_after

  • dispatch_after是來延遲執行的GCD方法,因爲在主線程中我們不能用sleep來延遲方法的調用,所以用dispatch_after是最合適的
  • dispatch_after能讓我們添加進隊列的任務延時執行,該函數並不是在指定時間後執行處理,而只是在指定時間追加處理到dispatch_queue
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(<#delayInSeconds#> * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        <#code to be executed after a specified delay#>
    });

多次執行 dispatch_apply

把一項任務提交到隊列中多次執行,具體是並行執行還是串行執行由隊列本身決定.注意,dispatch_apply不會立刻返回,在執行完畢後纔會返回,是同步的調用。

/*
    iterations  執行的次數  
    queue       提交到的隊列  
    block       執行的任務
 */
dispatch_apply(size_t iterations,
               dispatch_queue_t queue,
               ^{
        <#code to be executed#>
    });

阻塞 dispatch_barrier

dispatch_barrier_async用於等待前面的任務執行完畢後自己才執行,而它後面的任務需等待它完成之後才執行。一個典型的例子就是數據的讀寫,通常爲了防止文件讀寫導致衝突,我們會創建一個串行的隊列,所有的文件操作都是通過這個隊列來執行,比如FMDB,這樣就可以避免讀寫衝突。

- (void)dispatch_barrier_async
{
    
    //創建一個併發隊列
    dispatch_queue_t global_queue = dispatch_get_global_queue(0, 0);
    
    //異步把任務 放入 併發隊列
    dispatch_async(global_queue, ^{
        NSLog(@"A開始 %@",[NSThread currentThread]);
        //模擬請求耗時
        sleep(2);
        NSLog(@"A完成 %@",[NSThread currentThread]);
    });
    
    dispatch_async(global_queue, ^{
        NSLog(@"B開始 %@",[NSThread currentThread]);
        sleep(1);
        NSLog(@"B完成 %@",[NSThread currentThread]);
    });
    
    dispatch_barrier_async(global_queue, ^{
        NSLog(@"dispatch_barrier_async");
    });
    
    dispatch_async(global_queue, ^{
        NSLog(@"C開始 %@",[NSThread currentThread]);
        sleep(3);
        NSLog(@"C完成 %@",[NSThread currentThread]);
    });

}

我們將寫數據的操作放在dispatch_barrier_async中,這樣能確保在寫數據的時候會等待前面的讀操作完成,而後續的讀操作也會等到寫操作完成後才能繼續執行,提高文件讀寫的執行效率。

調度組 dispatch_group

當我們想在gcd queue中所有的任務執行完畢之後做些特定事情的時候,也就是隊列的同步問題,如果隊列是串行的話,那將該操作最後添加到隊列中即可,但如果隊列是並行隊列的話,這時候就可以利用dispatch_group來實現了,dispatch_group能很方便的解決同步的問題。dispatch_group_create可以創建一個group對象,然後可以添加block到該組裏面

  • dispatch_group_wait
    dispatch_group_wait會同步地等待group中所有的block執行完畢後才繼續執行,類似於dispatch_barrier
  • dispatch_group_notify
    功能與dispatch_group_wait類似,不過該過程是異步的,不會阻塞該線程,dispatch_group_notify有三個參數
void dispatch_group_notify(dispatch_group_t group, //要觀察的group
                         dispatch_queue_t queue,   //block執行的隊列
                         dispatch_block_t block);   //當group中所有任務執行完畢之後要執行的block
  • dispatch_group_enter && dispatch_group_leave
    假如我們不想使用dispatch_group_async異步的將任務丟到group中去執行,這時候就需要用到dispatch_group_enterdispatch_group_leave方法,這兩個方法要配對出現,以下這兩種方法是等價的:
dispatch_group_async(group, queue, ^{ 
});

等價於

dispatch_group_enter(group);
dispatch_async(queue, ^{
  dispatch_group_leave(group);
});

(具體用法在下面實際應用)


掛起隊列 dispatch_suspend/ 恢復隊列dispatch_resume

dispatch_suspenddispatch_resume提供了“掛起、恢復”隊列的功能簡單來說,就是可以暫停、恢復隊列上的任務。
但是這裏的“掛起”,只能掛起隊列後面正在排隊的任務,並不能讓當前正在執行的任務停止

- (void)dispatch_suspend_resume
{
    dispatch_queue_t queue = dispatch_queue_create("cn.jimmypeng", DISPATCH_QUEUE_SERIAL);
    //提交第一個block,延時5秒打印。
    dispatch_async(queue, ^{
        sleep(5);
        NSLog(@"After 5 seconds...");
    });
    //提交第二個block,也是延時5秒打印
    dispatch_async(queue, ^{
        sleep(5);
        NSLog(@"After 5 seconds again...");
    });
    //延時一秒
    NSLog(@"sleep 1 second...");
    sleep(1);
    //掛起隊列
    NSLog(@"suspend...");
    dispatch_suspend(queue);
    //延時10秒
    NSLog(@"sleep 10 second...");
    sleep(10);
    //恢復隊列
    NSLog(@"resume...");
    dispatch_resume(queue);
}

Log:
16:24:27.565 GCD[10824:343675] sleep 1 second...
16:24:28.566 GCD[10824:343675] suspend...
16:24:28.567 GCD[10824:343675] sleep 10 second...
16:24:32.569 GCD[10824:343750] After 5 seconds...
16:24:38.568 GCD[10824:343675] resume...
16:24:43.571 GCD[10824:343750] After 5 seconds again...

在dispatch_suspend掛起隊列後,第一個block還是在運行,並且正常輸出。


信號量 dispatch_semaphore

信號量(Semaphore),有時被稱爲信號燈,是在多線程環境下使用的一種設施,是可以用來保證兩個或多個關鍵代碼段不被併發調用。在進入一個關鍵代碼段之前,線程必須獲取一個信號量;一旦該關鍵代碼段完成了,那麼該線程必須釋放信號量。其它想進入該關鍵代碼段的線程必須等待直到第一個線程釋放信號量。爲了完成這個過程,需要創建一個信號量VI,然後將Acquire Semaphore VI以及Release Semaphore VI分別放置在每個關鍵代碼段的首末端。確認這些信號量VI引用的是初始創建的信號量。

但是這段文字可能過於抽象了,一般我們用停車的例子來闡釋信號量控制線程的過程。

以一個停車場的運作爲例。簡單起見,假設停車場只有三個車位,一開始三個車位都是空的。這時如果同時來了五輛車,看門人允許其中三輛直接進入,然後放下車攔,剩下的車則必須在入口等待,此後來的車也都不得不在入口處等待。這時,有一輛車離開停車場,看門人得知後,打開車攔,放入外面的一輛進去,如果又離開兩輛,則又可以放入兩輛,如此往復。
在這個停車場系統中,車位是公共資源,每輛車好比一個線程,看門人起的就是信號量的作用。

在GCD中有三個函數是semaphore的操作,
分別是:

  • dispatch_semaphore_create創建一個semaphore (創建車位,可以傳入參數創建多少個車位)
  • dispatch_semaphore_signal發送一個信號  (告訴看門人有一輛車走了,空出來一個車位)
  • dispatch_semaphore_wait等待信號 (看門人看到車來了,如果有空車位 讓車進去,沒有空車位,讓車按順序等待空車位,可以設置等待時間,設置之後過了等待時間,車直接開走,不再進入車庫)

(具體用法在下面實際應用)


GCD 進階實際應用場景

異步下載圖片 (異步請求,主線程刷新)

  • 單個任務
- (void)downLoadImage
{
    //下載圖片耗費時間所以在子線程上面執行
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"%@",[NSThread currentThread]);
        NSURL *url = [NSURL URLWithString:@"http://a.hiphotos.baidu.com/image/pic/item/b21c8701a18b87d6ff2ca7bc030828381f30fd23.jpg"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *img = [UIImage imageWithData:data];
        
     //刷新UI 在主線程刷新UI
        dispatch_async(dispatch_get_main_queue(), ^{
             NSLog(@"main_queue %@",[NSThread currentThread]);
          self.MyImageView.image = img;
        });
        
    });
}
  • 下載三首歌曲,不要求下載順序,但是三首歌曲全部下載完成之後,提示用戶需要在主線程 (調度組)
- (void)dispatch_group_demo1
{
    //創建一個調度組
    dispatch_group_t group = dispatch_group_create();
    
    //進入調度組
    dispatch_group_enter(group);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        //模擬請求耗時
        sleep(2);
        NSLog(@"A");
        //事件完成 離開調度組
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(1);
        NSLog(@"B");
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(3);
        NSLog(@"C");
        dispatch_group_leave(group);
    });
    
    //所有任務從調度組裏面拿出來 調用通知
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"完成");
    });
}
- (void)dispatch_group_demo2
{

    //調度組
    dispatch_group_t group = dispatch_group_create();
    
    /*
     參數1:調度組
     參數2:隊列
     參數3:任務
     */
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        sleep(1);
        NSLog(@"下載第1首歌曲");
    });
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        sleep(1);
        NSLog(@"下載第2首歌曲");
    });
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        sleep(1);
        NSLog(@"下載第3首歌曲");
    });
    //通知
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"歌曲下載完成");
    });

}

線程依賴 (異步串行/信號量)

  • 通過串行隊列進行線程依賴
- (void)dispatch_serial_queue
{
       NSLog(@"開始");
    
    //創建一個串行隊列
    dispatch_queue_t serial_queue = dispatch_queue_create("cn.jimmypeng", DISPATCH_QUEUE_SERIAL);
    
    //異步把任務 放入 串行隊列
    dispatch_async(serial_queue, ^{
        NSLog(@"A開始 %@",[NSThread currentThread]);
        //模擬請求耗時
        sleep(2);
        NSLog(@"A完成 %@",[NSThread currentThread]);
    });
    
    dispatch_async(serial_queue, ^{
        NSLog(@"B開始 %@",[NSThread currentThread]);
        sleep(1);
        NSLog(@"B完成 %@",[NSThread currentThread]);
    });
    
    dispatch_async(serial_queue, ^{
        NSLog(@"C開始 %@",[NSThread currentThread]);
        sleep(3);
        NSLog(@"C完成 %@",[NSThread currentThread]);
    });
    
    NSLog(@"完成");
}

Log:
16:09:06.520 GCD[10457:323508] 開始
16:09:06.520 GCD[10457:323508] 完成
16:09:06.520 GCD[10457:323550] A開始 <NSThread: 0x600000073880>{number = 3, name = (null)}
16:09:08.524 GCD[10457:323550] A完成 <NSThread: 0x600000073880>{number = 3, name = (null)}
16:09:08.524 GCD[10457:323550] B開始 <NSThread: 0x600000073880>{number = 3, name = (null)}
16:09:09.525 GCD[10457:323550] B完成 <NSThread: 0x600000073880>{number = 3, name = (null)}
16:09:09.525 GCD[10457:323550] C開始 <NSThread: 0x600000073880>{number = 3, name = (null)}
16:09:12.529 GCD[10457:323550] C完成 <NSThread: 0x600000073880>{number = 3, name = (null)}
  • 通過信號量進行線程依賴
- (void)dispatch_semaphore
{
    NSLog(@"開始");
    //創建一個信號量容器
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    
    //進入調度組
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"A開始 %@",[NSThread currentThread]);
        //模擬請求耗時
        sleep(2);
        NSLog(@"A完成 %@",[NSThread currentThread]);
        //事件完成 離開調度組
        dispatch_semaphore_signal(semaphore);
    });
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"B開始 %@",[NSThread currentThread]);
        sleep(1);
        NSLog(@"B完成 %@",[NSThread currentThread]);
        dispatch_semaphore_signal(semaphore);
    });
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"C開始 %@",[NSThread currentThread]);
        sleep(3);
        NSLog(@"C完成 %@",[NSThread currentThread]);
        dispatch_semaphore_signal(semaphore);
    });
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    
    NSLog(@"完成");
}

Log:
16:06:15.030 GCD[10383:320049] 開始
16:06:15.031 GCD[10383:320105] A開始 <NSThread: 0x60800007aa80>{number = 3, name = (null)}
16:06:17.036 GCD[10383:320105] A完成 <NSThread: 0x60800007aa80>{number = 3, name = (null)}
16:06:17.037 GCD[10383:320105] B開始 <NSThread: 0x60800007aa80>{number = 3, name = (null)}
16:06:18.042 GCD[10383:320105] B完成 <NSThread: 0x60800007aa80>{number = 3, name = (null)}
16:06:18.043 GCD[10383:320105] C開始 <NSThread: 0x60800007aa80>{number = 3, name = (null)}
16:06:21.044 GCD[10383:320105] C完成 <NSThread: 0x60800007aa80>{number = 3, name = (null)}
16:06:21.045 GCD[10383:320049] 完成

控制最大併發數 (信號量)

在iOS重寫GCD之後,GCD的最大併發數就一發不可收拾了,之前筆者測試過最大的情況下甚至會開100多條併發線程來處理事務,這個我覺得還是比較浪費的,但是GCD又沒有給出官方的設置最大併發數的方法,而在蘋果新退出的NSOperation裏面只要設置maxConcurrentOperationCount這個屬性就能控制最大併發數。不知道是不是蘋果的惡意想讓我們選擇更加面向對象的NSOperation,但是目前大部分的iOS開發者還是使用GCD的,所以下面跟大家說一下用GCD怎麼控制最大併發數

@implementation CustomOperationQueue

- (id)initWithConcurrentCount:(int)count
{
      self = [super init];
      if (self) {
        if (count < 1) count = 5;
        semaphore = dispatch_semaphore_create(count);
        queue = Dispatch_queue_create("cn.jimmypeng", DISPATCH_QUEUE_CONCURRENT);
      }
    return self;
}

- (id)init 
{
      return [self initWithConcurrentCount:5];
}

- (void)addTask:(CallBackBlock)block
{
    dispatch_async(queue, ^{
       dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
       dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), 0), ^{
            block();
            dispatch_semaphore_signal(semaphore);
        });
    });
}

@end

在 addTask:方法中

  1. 當前的初始庫存爲5
  2. 第一次調用dispatch_semaphore_wait的時候會直接執行下去,並減少一個庫存。每當完成一個任務之後,會執行到dispatch_semaphore_signal將庫存添加回去。
  3. 當執行的任務爲非常耗時的操作的時候,庫存不能及時地還回去。而dispatch_semaphore_wait在仍然執行,庫存最後會被減到0,這樣dispatch_semaphore_wait就只能進行等待直到前面的任務有執行完成將存庫有添加回去爲止。

如此便完成了併發量的控制!

快速遍歷

通常我們會用for循環遍歷,但是GCD給我們提供了快速迭代的方法dispatch_apply,使我們可以同時遍歷。比如說遍歷0~5這6個數字,for循環的做法是每次取出一個元素,逐個遍歷。dispatch_apply可以同時遍歷多個數字。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_apply(6, queue, ^(size_t index) {
    NSLog(@"%zd------%@",index, [NSThread currentThread]);
});

dispatch_semaphore GCD信號量

  • dispatch_semaphore 一共有三個方法

dispatch_semaphore_create => 創建一個信號量

dispatch_semaphore_signal => 發送一個信號

dispatch_semaphore_wait => 等待信號

關於信號量,一般可以用停車來比喻。
  停車場剩餘4個車位,那麼即使同時來了四輛車也能停的下。如果此時來了五輛車,那麼就有一輛需要等待。
  信號量的值就相當於剩餘車位的數目,dispatch_semaphore_wait函數就相當於來了一輛車,dispatch_semaphore_signal就相當於走了一輛車。停車位的剩餘數目在初始化的時候就已經指明瞭(dispatch_semaphore_create(long value)),
  調用一次dispatch_semaphore_signal,剩餘的車位就增加一個;調用一次dispatch_semaphore_wait剩餘車位就減少一個;
  當剩餘車位爲0時,再來車(即調用dispatch_semaphore_wait)就只能等待。有可能同時有幾輛車等待一個停車位。有些車主沒有耐心,給自己設定了一段等待時間,這段時間內等不到停車位就走了,如果等到了就開進去停車。而有些車主就像把車停在這,所以就一直等下去。

代碼示例:

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

NSLog(@"等待semaphore");

結果就是, 等待semaphore 這句話永遠不會輸出。原因有兩個

  1. 你初始化信號量的時候,並沒有庫存,也就是你傳入的值是0.
  2. 你傳入等待增加庫存的時間是 DISPATCH_TIME_FOREVER ,也就是說,除非有 地方運行了 dispatch_semaphore_signal 增加了庫存,否則我永遠等待下 去。 基於上述的兩個原因,導致了程序不往下走了。

  • 信號量的應用

1.測試異步網絡請求

在編寫單元測試中就可以:

- (void)downloadImageURLWithString:(NSString *)URLString
{
// 1
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0)
    NSURL *url = [NSURL URLWithString:URLString];
    __unused Photo *photo = [[Photo alloc] initwithURL:url withCompletionBlock:^(UIImage *image, NSError *error) {
      if (error) {
            XCTFail(@"%@ failed. %@", URLString, error);
        }
// 2
      dispatch_semaphore_signal(semaphore);
    }];

// 3
      dispatch_time_t timeoutTime = dispatch_time(DISPATCH_TIME_NOW, 5);
      if (dispatch_semaphore_wait(semaphore, timeoutTime)) {
          XCTFail(@"%@ timed out", URLString);
        }
}

2.控制併發量

@implementation CustomOperationQueue

- (id)initWithConcurrentCount:(int)count
{
      self = [super init];
      if (self) {
        if (count < 1) count = 5;
        semaphore = dispatch_semaphore_create(count);
        queue = Dispatch_queue_create("Jake", DISPATCH_QUEUE_CONCURRENT);
      }
    return self;
}

- (id)init 
{
      return [self initWithConcurrentCount:5];
}

- (void)addTask:(CallBackBlock)block
{
    dispatch_async(queue, ^{
       dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
       dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), 0), ^{
            block();
            dispatch_semaphore_signal(semaphore);
        });
    });
}

@end

在 addTask:方法中
1.當前的初始庫存爲5
2.第一次調用dispatch_semaphore_wait的時候會直接執行下去,並減少一個庫存。每當完成一個任務之後,會執行到dispatch_semaphore_signal將庫存添加回去。
3.當執行的任務爲非常耗時的操作的時候,庫存不能及時地還回去。而dispatch_semaphore_wait在仍然執行,庫存最後會被減到0,這樣dispatch_semaphore_wait就只能進行等待直到前面的任務有執行完成將存庫有添加回去爲止。

如此便完成了併發量的控制!

線程鎖的使用:
以下代碼來自YYModel

+ (instancetype)classInfoWithClass:(Class)cls {
    if (!cls) return nil;
    static CFMutableDictionaryRef classCache;
    static CFMutableDictionaryRef metaCache;
    static dispatch_once_t onceToken;
    static dispatch_semaphore_t lock;
    dispatch_once(&onceToken, ^{
        classCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        metaCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        lock = dispatch_semaphore_create(1);
    });
    dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
    YYClassInfo *info = CFDictionaryGetValue(class_isMetaClass(cls) ? metaCache : classCache, (__bridge const void *)(cls));
    if (info && info->_needUpdate) {
        [info _update];
    }
    dispatch_semaphore_signal(lock);
    if (!info) {
        info = [[YYClassInfo alloc] initWithClass:cls];
        if (info) {
            dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
            CFDictionarySetValue(info.isMeta ? metaCache : classCache, (__bridge const void *)(cls), (__bridge const void *)(info));
            dispatch_semaphore_signal(lock);
        }
    }
    return info;
}

 


 

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