let GCD deadlock
July 28th, 2010 | Published in CS
夏だし、 Grand Central Dispatch (GCD) をデッドロックさせてみたい。
さらっとぐぐって見つけた、デッドロックさせるコードはこんなかんじ:
void deadlock_1()
{
dispatch_queue_t q = dispatch_queue_create("org.deadbeaf.gcd3", NULL);
dispatch_sync(q, ^{
printf("in 1\n");
dispatch_sync(q, ^{
printf("in 2\n");
});
});
}
で、まぁたしかに処理がすすまなくなる。
でもこのコードからはなんだか、デッドロックというか自分を追い越せないだけな感じを受ける。ひとつのキューがあって、そこにつっこまれた人が、自分が終わる前に他のものをつっこんでいて、無限に待つという。
デッドロックというのは、2つ以上の共有資源があるときに、かたっぽ (A) を自分 (α) のものにしていながら、もうかたっぽのもの (B) をとろうとして、でも B はだれか (β) さんがロックしていて、で β さんは A がほしくて、というすくみの状況を言うんじゃなかったっけか。
というのを踏まえて、以下のように書いてみた。共有資源は 2 つ、 GCD の serial queue は、リソースを占有したいときに使うはず なので、 queue もそれぞれ用に 2 つ。
void deadlock_2()
{
__block int shared_resource_1 = 0;
__block int shared_resource_2 = 0;
dispatch_queue_t q1 = dispatch_queue_create("org.deadbeaf.gcd1", NULL);
dispatch_queue_t q2 = dispatch_queue_create("org.deadbeaf.gcd2", NULL);
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t the_q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_async(group, the_q, ^{
dispatch_sync(q1, ^{
printf("in q1\n");
dispatch_sync(q2, ^{
printf("hello from q1->q2\n");
shared_resource_2 = 3;
});
shared_resource_1 = 5;
});
});
dispatch_group_async(group, the_q, ^{
dispatch_sync(q2, ^{
printf("in q2\n");
dispatch_sync(q1, ^{
printf("hello from q2->q1\n");
shared_resource_1 = 11;
});
shared_resource_2 = 7;
});
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_release(group);
dispatch_release(q1);
dispatch_release(q2);
}
これだと、うまくいくときは
% ./gcd_deadlock
in q1
hello from q1->q2
in q2
hello from q2->q1
となるんだけど、タイミングによっては
./gcd_deadlock
in q1
in q2
....
と無限に待つ。デッドロックが起きているのだ。
まとめると、 GCD でもデッドロックはわりと容易に起こりうるなぁと。で、それがかんたんに検出できるんだったらいいんだけど、そこは pthread mutex における検出のむずかしさとあまり変わらないんじゃないかなぁというところ。