Почему странное поведение одновременной очереди?

Я пытаюсь понять параллельную очередь iOS GCD. Я сделал некоторый код для тестирования, но нашел что-то странное. код как показано ниже:

_syncQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);

for (int index = 0; index < 3; ++index) {
    dispatch_sync(_syncQueue, ^{

        NSLog(@"sync@@@@@@ >>>>    %d ",index);
        sleep(1);
        NSLog(@"sync@@@@@@ <<<<    %d ",index);
    });
}

for (int index = 3; index < 6; ++index) {
    dispatch_async(_syncQueue, ^{

        NSLog(@"sync===== >>>>    %d ",index);
        sleep(1);
        NSLog(@"sync===== <<<<    %d ",index);
    });
}

for (int index = 6; index < 9; ++index) {
    dispatch_sync(_syncQueue, ^{

        NSLog(@"sync***** >>>>    %d ",index);
        sleep(1);
        NSLog(@"sync***** <<<<    %d ",index);
    });
}

Результаты выполнения ниже:

sync@@@@@@ >>>>    0
sync@@@@@@ <<<<    0
sync@@@@@@ >>>>    1
sync@@@@@@ <<<<    1
sync@@@@@@ >>>>    2
sync@@@@@@ <<<<    2
sync***** >>>>    6
sync===== >>>>    4
sync===== >>>>    3
sync===== >>>>    5
sync***** <<<<    6
sync***** >>>>    7
sync===== <<<<    4
sync===== <<<<    5
sync===== <<<<    3
sync***** <<<<    7
sync***** >>>>    8
sync***** <<<<    8

Я в замешательстве и не могу понять, почему это так.

  1. почему до тех пор, пока 1-й цикл не будет полностью завершен, 2-й и 3-й цикл могут начать работать, для меня 1-й цикл должен быть прерван как минимум 2-м циклом, поскольку 2-й цикл может создавать новые потоки для выполнения.

  2. Почему третий цикл начинается раньше, чем второй?

  3. Почему 3-й цикл не может работать как 1-й цикл, который выполняется без прерывания другими задачами?

1 ответ

Решение

Вы спрашивали:

  1. почему до тех пор, пока 1-й цикл не будет полностью завершен, 2-й и 3-й цикл могут начать работать, для меня 1-й цикл должен быть прерван как минимум 2-м циклом, поскольку 2-й цикл может создавать новые потоки для выполнения.

Это потому, что вы использовали dispatch_sync, Это фактически говорит: "остановите текущий поток от продолжения, пока отправленная задача не завершится". Таким образом, этот первый цикл даже не перейдет к следующей итерации своего собственного цикла, пока задача не будет отправлена ​​с предыдущим dispatch_sync отделки.

Если вы посмотрите на диаграмму ниже, dispatch_sync звонки - это красные Ⓢ флаги. Вы можете видеть, что даже не отправляется вторая итерация этого первого цикла, пока не завершится первая отправленная задача.

  1. Почему третий цикл начинается раньше, чем второй?

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

Обратите внимание, что, хотя у вас нет никаких технических гарантий относительно того, будут ли задачи, отправленные вторым или третьим циклом, запускаться первыми, есть две интересные детали:

  1. Мы можем быть относительно уверены, что последующие итерации третьего цикла (а именно, итерации 7 и 8) не начнутся перед отправленными задачами второго цикла, потому что опять же вы отправляете все в третьем цикле синхронно. Так, например, он даже не будет пытаться отправить итерацию 7, пока не будет выполнено выполнение итерации 6 (тогда как второй цикл уже отправил свои задачи асинхронно, и эти задачи будут выполняться в этой параллельной очереди без изменений).

  2. Обратите внимание, что, хотя у вас нет никаких гарантий относительно времени выполнения задач, отправляемых вторым циклом, и первой задачи, отправляемой третьим циклом, на практике вы, как правило, увидите, что первая задача третьего цикла запускается быстрее из-за оптимизации, встроенной в dispatch_sync, dispatch_async используемый вторым циклом должен выполнять большую работу, а именно GCD должен получить рабочий поток из пула и запустить задачу в этом потоке. Но dispatch_sync третьего цикла, как оптимизация, часто просто запускает отправленную задачу в текущем потоке. (Если поток все равно должен ждать отправленную задачу, почему бы просто не использовать ее для запуска задачи и вообще избежать переключения контекста.)

    Это техническая деталь, о которой я бы посоветовал вам не беспокоиться, но он объясняет, почему вы часто видите dispatch_sync задачи начинаются быстрее, чем dispatch_async начался в той же параллельной очереди примерно в то же время.

Таким образом, на диаграмме ниже вызовы диспетчеризации (красный Ⓢ) для итераций 3-5 (второй цикл) и итерации 6 (первая итерация третьего цикла) происходят настолько близко друг к другу, что флаги накладываются друг на друга. Но вы можете увидеть время их в списке под графиком.

  1. Почему 3-й цикл не может работать как 1-й цикл, который выполняется без прерывания другими задачами?

Проблема не в том, что первый цикл запускался "без прерывания", а просто в том, что в очереди больше ничего не выполнялось, и поскольку он работал синхронно, больше ничего не собиралось запускаться до завершения цикла 1. Принимая во внимание, что третий цикл отправил итерацию № 6 почти в то же время, что и все итерации второго цикла #3–5.

Я думаю, что показательно взглянуть на временную шкалу (созданную инструментом Instruments "Точки интереса") этих девяти отправленных задач:

Первые три голубых задания представляют первый цикл. Фиолетовые задачи - вторая петля. Оранжевые задачи - третий цикл. dispatch_sync а также dispatch_async звонки обозначены красными флагами.

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

Другие вопросы по тегам