2015-03-31 64 views
0

我有一個遞歸函數,用於在併發隊列上調度新任務。我想限制同時計劃任務的數量,所以我使用一個信號量,以便每個任務都會等待,直到舊線程結束併發出信號燈信號。當所有正在運行的任務正在等待時,GCD死鎖不會啓動掛起的任務

但是我發現當達到最大運行線程數(64)並且它們都開始在信號量上等待時,隊列會死鎖。然後GCD不會開始新的任務,即使它的待處理隊列中有很多。

我在做什麼錯?這是我的代碼:

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification 
{ 
    dispatch_semaphore_t sem = dispatch_semaphore_create(10); 

    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); 

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^
       { 
        [self recurWithSemaphore:sem]; 
       }); 
} 

- (void)recurWithSemaphore:(dispatch_semaphore_t)sem 
{ 
    // do some lengthy work here... 

    // at this point we're done all but scheduling new tasks so let new tasks be created 
    dispatch_semaphore_signal(sem); 

    for (NSUInteger i = 0; i < 100; ++i) 
    { 
     // don't schedule new tasks until we have enough semaphore 
     dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); 

     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^
        { 
         [self recurWithSemaphore:sem]; 
        }); 
    } 
} 
+0

我已經考慮過NSOperationQueue,但限制併發線程的數量與限制* scheduled *任務的數量不同。計劃任務即使在未運行時也會消耗大量內存,因此我想推遲計劃任務的時間。 – egd 2015-03-31 17:22:24

+0

在我的代碼中,每個線程都會同時嘗試產生儘可能多的子任務,但由於與其他任務的爭用,它最終會在信號量上等待,因此沒有任何線程永遠不會退出。所以線程數量達到了最大GCD(約70),然後死鎖。從理論上講,當運行中的任務被阻塞時,GCD應該從掛起的隊列中啓動新的任務。我錯了嗎? – egd 2015-03-31 17:39:51

+0

是的,我已經嘗試使用串行「調度程序」隊列以受管制的方式啓動新線程,但讓所有併發線程與單個調度程序隊列同步是一個巨大的性能瓶頸。所以我正在嘗試使用信號量進行同步。 – egd 2015-03-31 17:43:56

回答

0

典型模式使用信號量時來控制訪問有限的資源是

  • 創建具有非零值信號;

  • 對於每一個任務:

    • 在啓動時,「等待」信號量(從而消耗可用的信號中的一個,或者如果一個不可用,等待一個);和

    • 完成後,「信號」信號量(使其可用於其他任務)。

所以,讓我們說,你想開百萬的任務,只有4個同時在任何給定的時間,你可以這樣做:

dispatch_semaphore_t semaphore = dispatch_semaphore_create(4); 

dispatch_queue_t queue = ... // some concurrent queue, either global or your own 

dispatch_async(queue, ^{ 
    for (long index = 0; index < 1000000; index++) { 
     dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); 
     dispatch_async(queue, ^{ 
      [self performSomeActionWithIndex:index completion:^{ 
       dispatch_semaphore_signal(semaphore); 
      }]; 
     }); 
    } 
}); 

很顯然,如果你動態地添加要執行更多任務,您可以將其從for循環更改爲while循環,檢查一些同步源,但是這個想法是相同的。

但關鍵的觀察是,我沒有performSomeActionWithIndex本身,它本身遞歸地創建任務本身(因爲然後你進入原始問題的死鎖情況,因爲任務停止,因爲它們無法啓動新任務)。

現在,我不知道你的問題是否可以重構爲這種模式,但如果可以的話,這可能是一個選擇。


順便說一句,爲了完整起見,我要指出的是,用於控制併發度的典型解決方案是使用操作隊列,而不是調度隊列,在這種情況下,你可以指定maxConcurrentOperationCount

正如您正確指出的那樣,存在內存影響。在我的測試中,每個計劃的操作至少佔用500個字節(在現實世界中可能更多),因此如果您確實有超過5,000-10,000個任務需要調度,操作隊列可能很快變得不切實際。如您所知,未來的讀者應參考併發編程指南:併發和應用程序設計中的Performance Implications部分。

我知道這不是一個可行的方法在你的情況,但我只提到它爲未來的讀者的利益。當需要控制併發程度時,我通常會建議使用操作隊列。如果您處理的任務太多,以至於無法合理地將它們安排在操作隊列中,我只會跳到上面所述的方法。

+0

感謝羅布,這個工程,現在請幫我理解爲什麼遞歸是我的代碼中的一個問題? – egd 2015-04-01 15:54:56

+0

由於任務被阻塞,您正在迅速到達死鎖點,等待安排子任務,直到一個先前任務的完成如果您將子任務的調度移動到專用的「調度程序」過程(如上所述),則可以解決此問題。但是,要小心你的模型需要多少內存來跟蹤這些計劃任務(否則你只是處理導致你拒絕操作隊列的另一個問題的表現)。 – Rob 2015-04-01 16:13:18

+0

在我的情況下,這需要「調度程序」在一個串行隊列中運行,並且所有併發子任務必須與其同步,與所有無任何限制地啓動子任務的任務相比,這會造成很大的瓶頸。我已經嘗試過這... – egd 2015-04-01 16:39:57