Подтвердить что ты не робот

Как создать тупик в Grand Central Dispatch?

В документах Apple говорится:

Важно: никогда не следует вызывать dispatch_sync или dispatch_sync_f функция из задачи, выполняемой в той же очереди, в которой вы находитесь планируя перейти к функции. Это особенно важно для последовательные очереди, которые гарантированно заторможены, но также должны быть избегать параллельных очередей.

Как вы пишете код, чтобы сделать именно это?

4b9b3361

Ответ 1

Умышленный тупик в определенной очереди:

dispatch_queue_t queue = dispatch_queue_create("my.label", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
    dispatch_sync(queue, ^{
        // outer block is waiting for this inner block to complete,
        // inner block won't start before outer block finishes
        // => deadlock
    });

    // this will never be reached
}); 

Здесь ясно, что внешний и внутренний блоки работают в одной очереди. В большинстве случаев это происходит в местах, где менее очевидно, в какой очереди работает вызывающая сторона dispatch_sync. Обычно это происходит в (глубоко) вложенном стеке, где вы выполняете код в некотором классе, который был первоначально запущен в определенной очереди, и случайно вы вызываете dispatch_sync в ту же очередь.

Ответ 2

Простой код, создающий тупик:

dispatch_queue_t q = dispatch_queue_create("deadlock queue", DISPATCH_QUEUE_SERIAL);

NSLog(@"1");
dispatch_async(q, ^{
    NSLog(@"2");
    dispatch_sync(q, ^{
        NSLog(@"3");
    });
    NSLog(@"4");
});
NSLog(@"5");

Выход журнала:

1
5
2

Здесь внутренний блок планируется запустить в последовательной очереди q, но он не может работать до тех пор, пока текущий блок не будет завершен, а текущий блок, в свою очередь, ждет завершения внутренней обработки, как мы это называли синхронно.

Ответ 3

Самый простой способ блокировки - dispatch_sync в текущей очереди:

dispatch_sync(dispatch_get_current_queue(), ^{});

Это блокирует, когда текущая очередь представляет собой последовательную очередь, например главную очередь.

Ответ 4

В последнем синтаксисе Swift:

let queue = DispatchQueue(label: "label")
queue.async {
    queue.sync {
        // outer block is waiting for this inner block to complete,
        // inner block won't start before outer block finishes
        // => deadlock
    }
    // this will never be reached
}

Ответ 5

Интервьюеры часто спрашивают: "Какой самый простой способ вызвать тупик?"

Obj-C:

dispatch_sync(dispatch_get_main_queue(), ^{});

Swift:

DispatchQueue.main.sync {}

Вызов sync из основного потока вызовет взаимоблокировку, поскольку основная очередь является последовательной очередью, и sync останавливает текущее выполнение очереди до тех пор, пока не завершится пройденный блок/закрытие.

Ответ 6

В Swift 4.2 вы можете вызвать тупик, используя следующий фрагмент кода:

let aSerialQueue = DispatchQueue(label: "my.label")

aSerialQueue.sync {
    // The code inside this closure will be executed synchronously.
    aSerialQueue.sync {
        // The code inside this closure should also be executed synchronously and on the same queue that is still executing the outer closure ==> It will keep waiting for it to finish ==> it will never be executed ==> Deadlock.
    }
}

Ответ 7

Если кому-то любопытно, параллельная очередь НЕ блокируется, если sync вызывается для той же очереди. Я знаю, это очевидно, но мне нужно было подтвердить, что только последовательные очереди ведут себя таким образом 😅

Работает:

let q = DispatchQueue(label: "myQueue", attributes: .concurrent)

q.async {
    print("work async start")
    q.sync {
        print("work sync in async")
    }
    print("work async end")
}

q.sync {
    print("work sync")
}

print("done")

Сбой:

Инициализировать q как let q = DispatchQueue(label: "myQueue")//implicitly serial queue