5つのケースでGCDのデッドロックがわかります

12604 ワード

デッドロックは、マルチスレッドを使用する場合に注意しなければならない問題です.このブログは他の多くのブログの例を参考にして、GCDのデッドロック問題を説明しています.
 
シリアルとパラレル
GCDを使用する場合は、処理するタスクをBlockに配置し、対応するキューに追加します.このキューはDispatch Queueと呼ばれます.しかし、2つのDispatch Queueに存在し、1つは、前の実行が完了するのを待って、次のSerial Dispatch Queueを実行することであり、これをシリアルキューと呼ぶ.もう1つは、前の実行が完了する必要がなく、次のConcurrent Dispatch Queueを実行することができ、パラレルキューと呼ばれます.どちらもFIFOの原則に従います.
簡単な例を挙げると、3つのタスクで1、2、3が出力され、シリアルキュー出力は秩序ある1、2、3であるが、パラレルキューの前後順序は必ずしもそうではない.
では、パラレルキューはどのように実行されているのでしょうか.
複数のタスクの処理は同時に実行できますが、パラレルキューの処理量は、現在のシステムステータスに基づいています.現在のシステム状態で最大2つのタスクを処理すると、1、2が前に並び、3がいつ操作されるかは、1または2が先に完了し、3が後ろに続くかによって決まります.
シリアルとパラレルは簡単に言えますが、それらの技術点についてはまだ多く、自分で理解することができます.
 
同期と非同期
シリアルとパラレルはキューであり、同期と非同期はスレッドです.最大の違いは、同期スレッドが現在のスレッドをブロックするには、同期スレッドのタスクの実行が完了するのを待たなければならず、戻ってから次のタスクを実行することができません.非同期スレッドは待つ必要はありません.
これらの言葉だけではなかなか理解できないので、その後はいろいろなケースを用意し、分析しながら理解することができます.
 
GCD API
GCD APIが多く、ここでは本明細書で使用したもののみを紹介します.
 
1.システム標準で提供される2つのキュー
//

dispatch_get_global_queue

//  , , , 

dispatch_get_main_queue

2.それ以外に、自分でキューを生成することもできます
//  DISPATCH_QUEUE_SERIAL , 
dispatch_queue_create("com.demo.serialQueue", DISPATCH_QUEUE_SERIAL)

//
dispatch_queue_create("com.demo.concurrentQueue", DISPATCH_QUEUE_CONCURRENT)

次に、非同期スレッドの作成を同期します.
dispatch_sync(..., ^(block)) //  

dispatch_async(..., ^(block)) //  

 
ケースと分析
上記の知識を基本的に理解していると仮定し、次にケーススタディの段階に入ります.
 
ケース1:
NSLog(@"1"); //  1

dispatch_sync(dispatch_get_main_queue(), ^{

    NSLog(@"2"); //  2

});

NSLog(@"3"); //  3

結果、コンソール出力:
1

分析:
  • dispatch_syncは同期スレッドであることを示す.
  • dispatch_get_main_Queueは、プライマリ・スレッドで実行されるプライマリ・キュー列を表します.
  • タスク2は、スレッドを同期するタスクである.

  • まずタスク1を実行します.これは間違いなく問題ありません.ただ、次に、プログラムが同期スレッドに遭遇すると、タスク2の実行が完了するのを待って、タスク3を実行します.しかし、これはキューで、タスクが来て、もちろんタスクをキューの最後に追加して、FIFOの原則に従ってタスクを実行します.では、今タスク2が最後に追加され、タスク3がタスク2の前に並んでいます.問題が来ました.
     3 2 , 2 3 , 2 3 , 。【 , 】 。

     
    ケース2:
    NSLog(@"1"); //  1
    
    dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    
        NSLog(@"2"); //  2
    
    });
    
    NSLog(@"3"); //  3

    結果、コンソール出力:
    1
    2
    3

    分析:
    まずタスク1を実行し、次に同期スレッドに遭遇し、プログラムが待機します.タスク2の実行が完了するまで、タスク3の実行を続行できません.dispatch_からget_global_queueは、タスク2がグローバルなパラレルキューに組み込まれていることを示し、パラレルキューがタスク2を実行した後、プライマリキュー列に戻り、タスク3を実行し続ける.
     
    ケース3:
    dispatch_queue_t queue = dispatch_queue_create("com.demo.serialQueue", DISPATCH_QUEUE_SERIAL);
    
    NSLog(@"1"); //  1
    
    dispatch_async(queue, ^{
    
        NSLog(@"2"); //  2
    
        dispatch_sync(queue, ^{
    
            NSLog(@"3"); //  3
    
        });
    
        NSLog(@"4"); //  4
    
    });
    
    NSLog(@"5"); //  5

    結果、コンソール出力:
    1
    5
    2
    // 5 2 

    分析:
    このケースでは、システムが提供するシリアルまたはパラレルキューを使用するのではなく、自分でdispatch_を通過します.queue_create関数はDISPATCHを作成しました.QUEUE_SERIALのシリアルキュー.
  • タスク1を実行します.
  • 非同期スレッドに遭遇し、【タスク2、同期スレッド、タスク4】をシリアルキューに追加します.非同期スレッドであるため、プライマリスレッド内のタスク5は、非同期スレッド内のすべてのタスクの完了を待つ必要はない.
  • タスク5は待つ必要がないので、2と5の出力順序は確定できない.
  • タスク2の実行が完了すると、同期スレッドに遭遇し、このとき、タスク3をシリアルキューに追加する.
  • はまた、タスク4がタスク3よりも早くシリアルキューに参加するため、タスク3は、タスク4が完了するまで待機しなければ実行できない.ただし、タスク3が存在する同期スレッドはブロックされるため、タスク4は、タスク3の実行が完了してから実行しなければならない.これはまた無限の待機に陥り、デッドロックをもたらした.

  • ケース4:
    NSLog(@"1"); //  1
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
    
        NSLog(@"2"); //  2
    
        dispatch_sync(dispatch_get_main_queue(), ^{
        
            NSLog(@"3"); //  3
    
        });
    
        NSLog(@"4"); //  4
    
    });
    
    NSLog(@"5"); //  5    

    結果、コンソール出力:
    1
    2
    5
    3
    4
    // 5 2 

    分析:
    まず、【タスク1、非同期スレッド、タスク5】をMain Queueに加え、非同期スレッドのタスクは【タスク2、同期スレッド、タスク4】です.
    したがって,タスク1を先に実行し,非同期スレッド中のタスクをGlobal Queueに加えることになるが,非同期スレッドであるため,タスク5は待つ必要がなく,結果として2と5の出力順序が一定ではない.
    次に、非同期スレッドのタスク実行順序を見ます.タスク2の実行が完了すると、同期スレッドに遭遇します.同期スレッド内のタスクをMain Queueに加えると,そのときに加えられたタスク3はタスク5の後になる.
    タスク3の実行が完了すると、ブロックがなくなり、プログラムはタスク4を実行し続ける.
    以上の分析から、得られたいくつかの結果:1が最初に実行される;2と5の順序は必ずしもそうではない.4は必ず3の後ろにあります.
     
    ケース5:
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
    
        NSLog(@"1"); //  1
    
        dispatch_sync(dispatch_get_main_queue(), ^{
    
            NSLog(@"2"); //  2
    
        });
    
        NSLog(@"3"); //  3
    
    });
    
    NSLog(@"4"); //  4
    
    while (1) {
    
    }
    
    NSLog(@"5"); //  5    

    結果、コンソール出力:
    1
    4
    // 1 4 

    分析:
    上記のいくつかのケースの分析と同様に、まず、どのタスクがMain Queueに追加されているかを見てみましょう.【非同期スレッド、タスク4、デッドサイクル、タスク5】です.
    Global Queue非同期スレッドに加わるタスクは、【タスク1、同期スレッド、タスク3】である. 
    1つ目は非同期スレッドであり,タスク4は待つ必要がないため,結果としてタスク1とタスク4の順序は必ずしも一定ではない.
    タスク4が完了すると,プログラムはデッドサイクルに入り,Main Queueがブロックする.ただしGlobal Queueに加わる非同期スレッドは影響を受けず、タスク1の後の同期スレッドの実行を継続する. 
    同期スレッドでは、タスク2がプライマリスレッドに追加され、タスク3は、タスク2が完了してから実行されるのを待つ.このときのメインスレッドは,すでにデッドサイクルでブロックされている.したがってタスク2は実行できず、もちろんタスク3も実行できず、デッドサイクル後のタスク5も実行されない.
    最終的には,1と4の順序が不定の結果しか得られなかった.
    リファレンス
    http://www.jianshu.com/p/0b0d9b1f1f19
    http://www.cnblogs.com/tangbinblog/p/4133481.html