Поскольку эта проблема вызывала у меня много горя на протяжении многих лет, я решила, что поделюсь преимуществом этого страдания опыта:
Каждый раз, когда вы блокируете (включая вызов sleep
) в рабочем блоке *, представленном в GCD, вы создаете ситуацию с возможностью истощения потока. Хуже того, если блокировка вашего рабочего модуля происходит из-за использования примитивов синхронизации (то есть семафоров, блокировок и т. Д.) Или если несколько рабочих блоков заблокированы с помощью этих примитивов, голодание потока может вызвать взаимоблокировку. Еще через минуту, но вот короткая версия:
Если вы блокируете в рабочих единицах, представленных в GCD:
- В лучшем случае вы используете GCD неэффективно.
- В худшем случае вы подвергаете себя риску
голод и, следовательно, (потенциально) тупики.
И вот почему: ОС имеет ограничение на количество процессов для каждого процесса. Изменение ограничения потока для каждого процесса не является невозможным, но на практике это редко стоит того. GCD имеет свой собственный предел ширины очереди (количество потоков, которые он будет использовать для обслуживания параллельной очереди). (Несмотря на то, что детали сложны, стоит отметить, что создание нескольких параллельных очередей, вообще говоря, не сможет обойти этот предел.) По определению, предел GCD ниже, чем предел потока для процесса. Кроме того, ограничение ширины очереди GCD является недокументированной деталью реализации. Опытным путем на момент написания этой статьи в OS X я заметил, что ограничение составляет 64. Поскольку это ограничение является недокументированной деталью реализации, вы не можете рассчитывать, зная, что это такое. (Также невозможно изменить его с помощью общедоступного API.) В идеале, ваш код должен быть написан так, чтобы он по-прежнему выполнялся до завершения, хотя и медленно, если GCD изменил предел ширины очереди на 1. (Это также может быть утверждал, что то же самое должно быть правдой, даже если этот один поток также является основным потоком, но это делает задачу излишне сложной, и кажется безопасным предположить, что всегда будет хотя бы один фоновый поток, поскольку вряд ли стоило бы использовать GCD, если бы не было.)
Как бы вы это сделали? Если вы блокируете во время ожидания ввода-вывода, вы можете подумать о переводе своего кода для использования семейства вызовов dispatch_io
. Для ситуации ожидания с квази-занятым временем (т. Е. Первоначального вопроса) вы можете использовать dispatch_after
, чтобы проверить что-либо через определенное количество времени. Для других случаев могут подойти таймеры отправки или источники отправки.
Я буду первым, кто признает, что не всегда практично (не говоря уже о целесообразности) полностью избегать блокировок в рабочих единицах, представленных в GCD. Кроме того, комбинация блочного синтаксиса GCD и Objective-C делает очень простым написание выразительного, легко читаемого кода для избежания ситуаций, в которых вы могли бы блокировать поток пользовательского интерфейса, перемещая блокирующие / синхронные операции в фоновый поток. С точки зрения ускорения разработки, этот шаблон чрезвычайно полезен, и я использую его все время. Тем не менее, стоит знать, что постановка в очередь рабочего блока с GCD, который блокирует, потребляет некоторое количество конечного ресурса (т. Е. Числа доступных потоков), размер которого вы, педантично говоря, не можете знать (потому что это недокументированная деталь реализации и может изменить в любое время) и, практически говоря, не может контролировать (потому что вы не можете установить / изменить ширину очереди GCD с помощью публичного API.)
Относительно первоначального вопроса: Ожидание занятости (например, по телефону sleep
или usleep
) является одним из большинства , которых можно избежать, и наименьших оправданных способов. заблокировать в рабочем блоке, представленном в GCD. Я пойду дальше и сделаю смелое утверждение о том, что всегда всегда является лучшим, хотя и менее быстрым в разработке, способом реализации любой операции, которая может быть реализована путем ожидания занятости в рабочей единице GCD.
* Я использую «рабочий блок» для ссылки на блоки Objective-C или указатели / аргументы функций, переданные в GCD для выполнения, чтобы ограничить путаницу с «блокировкой» работы, под которой я подразумеваю «выполнение чего-то, чтов результате ваш поток будет приостановлен в ядре ".