Управление потоком

Каждый процесс (приложение) в OS X или iOS составлен из одного или более потоков, каждый из которых представляет единственный путь выполнения через код приложения. Каждое приложение запускается с единственного потока, запускающего приложение main функция. Приложения могут породить дополнительные потоки, каждый из которых выполняет код определенной функции.

Когда приложение порождает новый поток, тот поток становится независимой сущностью в пространстве процесса приложения. Каждый поток имеет свой собственный стек выполнения и планируется в течение времени выполнения отдельно ядром. Поток может связаться с другими потоками и другими процессами, выполнить операции I/O и сделать что-либо еще, что Вам, возможно, понадобился бы он, чтобы сделать. Поскольку они в том же пространстве процесса, однако, все потоки в единственном приложении совместно используют то же пространство виртуальной памяти и имеют те же права доступа как сам процесс.

Эта глава обеспечивает обзор технологий потока, доступных в OS X и iOS вместе с примерами того, как использовать те технологии в Ваших приложениях.

Затраты потока

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

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

Табличные 2-1  затраты создания Потока

Элемент

Приблизительная стоимость

Примечания

Структуры данных ядра

Приблизительно 1 КБ

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

Стековое пространство

512 КБ (вторичные потоки)

8 МБ (OS X основной поток)

1 МБ (iOS основной поток)

Минимальный позволенный размер штабеля для вторичных потоков составляет 16 КБ, и размер штабеля должен быть кратным числом 4 КБ. Пространство для этой памяти обойдено в Вашем пространстве процесса во время создания потока, но фактические страницы, связанные с той памятью, не создаются, пока они не необходимы.

Время создания

Приблизительно 90 микросекунд

Это значение отражает время между начальным вызовом для создания потока и время, в которое подпрограмма точки входа потока начала выполняться. Числа были определены путем анализа средних и средних значений, сгенерированных во время создания потока на основанной на Intel iMac с 2 процессорами GHz Core Duo и 1 ГБ RAM рабочий OS X v10.5.

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

Создание потока

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

Используя NSThread

Существует два способа создать поток с помощью NSThread класс:

  • Используйте detachNewThreadSelector:toTarget:withObject: метод класса породить новый поток.

  • Создайте новое NSThread возразите и вызовите start метод. (Поддерживаемый только в iOS и OS X v10.5 и позже.)

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

Поскольку detachNewThreadSelector:toTarget:withObject: метод поддерживается во всех версиях OS X, это часто считается в существующих приложениях Какао тем использованием потоками. Для отсоединения нового потока Вы просто обеспечиваете имя метода (указанный как селектор), что Вы хотите использовать в качестве точки входа потока, объект, определяющий тот метод и любые данные, которые Вы хотите передать потоку при запуске. Следующий пример показывает основной вызов этого метода, порождающего поток с помощью пользовательского метода текущего объекта.

[NSThread detachNewThreadSelector:@selector(myThreadMainMethod:) toTarget:self withObject:nil];

До OS X v10.5, Вы использовали NSThread класс прежде всего для порождения потоков. Несмотря на то, что Вы могли добраться NSThread объект и доступ некоторые атрибуты потока, Вы могли только сделать так от самого потока после того, как это работало. В OS X v10.5, поддержка была добавлена для создания NSThread объекты, сразу не порождая соответствующий новый поток. (Эта поддержка также доступна в iOS.) Эта поддержка позволила получить и установить различные атрибуты потока до запуска потока. Это также позволило использовать тот объект потока относиться к рабочему потоку позже.

Простой способ инициализировать NSThread объект в OS X v10.5 и позже состоит в том, чтобы использовать initWithTarget:selector:object: метод. Этот метод берет ту же самую информацию в качестве detachNewThreadSelector:toTarget:withObject: метод и использование это для инициализации нового NSThread экземпляр. Это не запускает поток, как бы то ни было. Для запуска потока Вы вызываете объект потока start метод явно, как показано в следующем примере:

NSThread* myThread = [[NSThread alloc] initWithTarget:self
                                        selector:@selector(myThreadMainMethod:)
                                        object:nil];
[myThread start];  // Actually create the thread

Если Вы имеете NSThread возразите, чей поток в настоящее время работает, один способ, которым можно отправить сообщения в тот поток, состоит в том, чтобы использовать performSelector:onThread:withObject:waitUntilDone: метод почти любого объекта в Вашем приложении. Поддержка выполнения селекторов на потоках (кроме основного потока) была представлена в OS X v10.5 и является удобным способом связаться между потоками. (Эта поддержка также доступна в iOS.) Сообщения Вы отправляете, использование этого метода выполняются непосредственно другим потоком как часть его нормальной обработки цикла выполнения. (Конечно, это действительно означает, что целевой поток должен работать в его цикле выполнения; посмотрите Циклы Выполнения.) Вам, возможно, все еще понадобится некоторая форма синхронизации, когда Вы передаете этот путь, но это более просто, чем установка коммуникационных портов между потоками.

Для списка других способов коммуникации потока посмотрите Установку Отдельного состояния Потока.

Используя потоки POSIX

OS X и iOS предоставляют поддержку на базе С для создания потоков с помощью потока POSIX API. Если Вы пишете свое программное обеспечение для многократных платформ, эта технология может фактически использоваться в любом типе приложения (включая Сенсорные приложения Какао и Какао) и могла бы быть более удобной. Подпрограмму POSIX, которую Вы используете для создания потоков, вызывают, соответственно достаточно, pthread_create.

Перечисление 2-1 показывает две пользовательских функции для создания потока с помощью вызовов POSIX. LaunchThread функция создает новый поток, основная подпрограмма которого реализована в PosixThreadMainRoutine функция. Поскольку POSIX создает потоки как joinable по умолчанию, этот пример изменяет атрибуты потока для создания отдельного потока. Отмечание потока, как отсоединено дает системе шанс предъявить претензии в отношении ресурсов для того потока сразу, когда это выходит.

Перечисление 2-1  , Создающее поток в C

#include <assert.h>
#include <pthread.h>
 
void* PosixThreadMainRoutine(void* data)
{
    // Do some work here.
 
    return NULL;
}
 
void LaunchThread()
{
    // Create the thread using POSIX routines.
    pthread_attr_t  attr;
    pthread_t       posixThreadID;
    int             returnVal;
 
    returnVal = pthread_attr_init(&attr);
    assert(!returnVal);
    returnVal = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    assert(!returnVal);
 
    int     threadError = pthread_create(&posixThreadID, &attr, &PosixThreadMainRoutine, NULL);
 
    returnVal = pthread_attr_destroy(&attr);
    assert(!returnVal);
    if (threadError != 0)
    {
         // Report an error.
    }
}

Если Вы добавляете код от предыдущего перечисления до одного из Ваших исходных файлов и вызываете LaunchThread функция, это создало бы новый отдельный поток в Вашем приложении. Конечно, создаваемое использование новых потоков этого кода не сделает ничего полезного. Потоки запустились бы и почти сразу вышли бы. Для создания вещей более интересными необходимо было бы добавить код к PosixThreadMainRoutine функция для выполнения некоторой фактической работы. Чтобы гарантировать, что поток знает что работу сделать, можно передать его указатель на некоторые данные во время создания. Вы передаете этот указатель как последний параметр pthread_create функция.

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

Для получения дополнительной информации о функциях потока POSIX, посмотрите pthread страница справочника.

Используя NSObject для порождения потока

В iOS и OS X v10.5 и позже, все объекты имеют возможность породить новый поток и использовать его для выполнения одного из их методов. performSelectorInBackground:withObject: метод создает новый отдельный поток и использует указанный метод в качестве точки входа для нового потока. Например, если у Вас есть некоторый объект (представленный переменной myObj) и тому объекту вызвали метод doSomething то, что Вы хотите работать в фоновом потоке, Вы могли использовать следующий код, чтобы сделать это:

[myObj performSelectorInBackground:@selector(doSomething) withObject:nil];

Если Вы вызвали, эффект вызова этого метода совпадает с detachNewThreadSelector:toTarget:withObject: метод NSThread с текущим объектом, селектором и объектом параметра как параметры. Новый поток сразу порожден с помощью конфигурации по умолчанию и начинает работать. В селекторе необходимо сконфигурировать поток так же, как Вы были бы любой поток. Например, необходимо было бы установить пул автовыпуска (если бы Вы не использовали сборку «мусора»), и сконфигурируйте цикл выполнения потока, если Вы запланировали использовать его. Для получения информации о том, как сконфигурировать новые потоки, посмотрите Атрибуты Потока Конфигурирования.

Используя потоки POSIX в приложении какао

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

Защита платформ какао

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

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

Если Вы не уверены, думает ли Какао, что Ваше приложение многопоточно или нет, можно использовать isMultiThreaded метод NSThread проверять.

Смешивание POSIX и блокировок какао

Безопасно использовать смесь POSIX и блокировок Какао в том же приложении. Блокировка какао и объекты условия являются по существу просто обертками для взаимных исключений POSIX и условий. Для данной блокировки, однако, необходимо всегда использовать тот же интерфейс, чтобы создать и управлять той блокировкой. Другими словами, Вы не можете использовать Какао NSLock объект управлять взаимным исключением Вы создали использование pthread_mutex_init функция, и наоборот.

Конфигурирование атрибутов потока

После создания потока, и иногда прежде можно хотеть сконфигурировать различные части среды потока. Следующие разделы описывают некоторые изменения, которые можно внести и когда Вы могли бы сделать их.

Конфигурирование размера штабеля потока

Для каждого нового потока Вы создаете, система выделяет определенный объем памяти в Вашем пространстве процесса для действия как штабель для того потока. Штабель управляет стековыми фреймами и также, где объявляются любые локальные переменные для потока. Объем памяти, выделенный для потоков, перечислен в Затратах Потока.

Если Вы хотите изменить размер штабеля данного потока, необходимо сделать так перед созданием потока. Все технологии поточной обработки обеспечивают некоторый способ установить размер штабеля, несмотря на то, что устанавливая использование размера штабеля NSThread доступно только в iOS и OS X v10.5 и позже. Таблица 2-2 перечисляет различные варианты для каждой технологии.

Таблица 2-2  , Устанавливающая размер штабеля потока

Технология

Опция

Какао

В iOS и OS X v10.5 и позже, выделите и инициализируйте NSThread объект (не используют detachNewThreadSelector:toTarget:withObject: метод). Прежде, чем вызвать start метод объекта потока, используйте setStackSize: метод для указания нового размера штабеля.

POSIX

Создайте новое pthread_attr_t структура и использование pthread_attr_setstacksize функционируйте для изменения размера штабеля по умолчанию. Передайте атрибуты pthread_create функционируйте при создании потока.

Multiprocessing Services

Передайте надлежащее значение размера штабеля MPCreateTask функционируйте при создании потока.

Конфигурирование локальной памяти потока

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

Какао и POSIX хранят словарь потока по-разному, таким образом, Вы не можете вызовы смешивания и подгонки к этим двум технологиям. Пока Вы придерживаетесь одной технологии в Вашем коде потока, однако, конечные результаты должны быть подобными. В Какао Вы используете threadDictionary метод NSThread объект получить NSMutableDictionary объект, к которому можно добавить любые ключи, требуемые потоком. В POSIX Вы используете pthread_setspecific и pthread_getspecific функции, чтобы установить и получить ключи и значения Вашего потока.

Установка отдельного состояния потока

Большинство высокоуровневых технологий потока создает отсоединенные потоки по умолчанию. В большинстве случаев отдельные потоки предпочтены, потому что они позволяют системе высвобождать структуры данных потока непосредственно после завершения потока. Отдельные потоки также не требуют явных взаимодействий с Вашей программой. Средние значения получения результатов потока оставляют Вашему усмотрению. Для сравнения система не предъявляет претензии в отношении ресурсов для joinable потоков, пока другой поток явно не присоединяется к тому потоку, процесс, который может блокировать поток, выполняющий соединение.

Можно думать о joinable потоках как сродни дочерним потокам. Несмотря на то, что они все еще работают как независимые потоки, к joinable потоку должен присоединиться другой поток, прежде чем в отношении его ресурсов сможет предъявить претензии система. Потоки Joinable также обеспечивают явный способ передать данные от выходящего потока до другого потока. Непосредственно перед тем, как это выходит, joinable поток может передать указатель данных или другое возвращаемое значение к pthread_exit функция. Другой поток может тогда требовать этих данных путем вызова pthread_join функция.

Если Вы действительно хотите создать joinable потоки, единственный способ сделать так использует потоки POSIX. POSIX создает потоки как joinable по умолчанию. Для маркировки потока, как отсоединено или joinable измените атрибуты потока с помощью pthread_attr_setdetachstate функция до создания потока. После того, как поток начинается, можно изменить joinable поток на отдельный поток путем вызова pthread_detach функция. Для получения дополнительной информации об этих функциях потока POSIX, посмотрите pthread страница справочника. Для получения информации о том, как присоединиться к потоку, посмотрите pthread_join страница справочника.

Установка приоритета потока

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

Если Вы действительно хотите изменить приоритеты потока, и Какао и POSIX обеспечивают способ сделать так. Для потоков Какао можно использовать setThreadPriority: метод класса NSThread установить приоритет в настоящее время рабочего потока. Для потоков POSIX Вы используете pthread_setschedparam функция. Для получения дополнительной информации см. Ссылку класса NSThread или pthread_setschedparam страница справочника.

Запись подпрограммы записи потока

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

Создание пула автовыпуска

Приложения, соединяющиеся в платформах Objective C обычно, должны создавать по крайней мере один пул автовыпуска в каждом из их потоков. Если приложение использует управляемую модель — где приложение обрабатывает сохранение и выпуск объектов — пул автовыпуска ловит любые объекты, автовыпущенные от того потока.

Если приложение использует сборку «мусора» вместо модели управляемой памяти, создание пула автовыпуска не строго необходимо. Присутствие пула автовыпуска в собравшем «мусор» приложении не вредно, и по большей части просто проигнорировано. Позволяется для случаев, где модуль кода должен поддерживать и сборку «мусора» и модель управляемой памяти. Если приложение запущено с включенной сборкой «мусора», в таком случае пул автовыпуска должен присутствовать для поддержки кода модели управляемой памяти и просто проигнорирован.

Если Ваше приложение использует модель управляемой памяти, создавание пула автовыпуска должно быть первой вещью, которую Вы делаете в своей подпрограмме записи потока. Точно так же уничтожение этого пула автовыпуска должно быть последней вещью, которую Вы делаете в своем потоке. Этот пул гарантирует, что автовыпущенные объекты пойманы, несмотря на то, что он не выпускает их, пока не выходит сам поток. Перечисление 2-2 показывает структуру основной подпрограммы записи потока, использующей пул автовыпуска.

Перечисление 2-2  , Определяющее Вашу подпрограмму точки входа потока

- (void)myThreadMainRoutine
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Top-level pool
 
    // Do thread work here.
 
    [pool release];  // Release the objects in the pool.
}

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

Для получения дополнительной информации о пулах управления памятью и автовыпуска см. Усовершенствованное Руководство по программированию управления памятью.

Установка обработчика исключений

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

Можно использовать или C++ или стиль обработки исключений Objective C при разрабатывании проекта в XCode. Для получения информации об установке, как повысить и поймать исключения в Objective C, посмотрите, что Исключение Программирует Темы.

Установка цикла выполнения

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

OS X и iOS предоставляют встроенную поддержку для реализации выполненных циклов в каждом потоке. Платформы приложения запускают цикл выполнения основного потока Вашего приложения автоматически. При создании каких-либо вторичных потоков необходимо сконфигурировать цикл выполнения и запустить его вручную.

Для получения информации об использовании и конфигурирование выполненных циклов, посмотрите Циклы Выполнения.

Завершение потока

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

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

Один способ реагировать на сообщения отмены состоит в том, чтобы использовать входной источник цикла выполнения для получения таких сообщений. Перечисление 2-3 показывает структуру того, как этот код мог бы посмотреть в основной подпрограмме записи Вашего потока. (Пример показывает, что основной цикл делит на части только, и не включает шаги для установки пула автовыпуска или конфигурирования фактической работы, чтобы сделать.) Пример устанавливает пользовательский входной источник на цикле выполнения, который по-видимому может быть передан от другого Ваших потоков; дополнительные сведения об установке входных источников см. в Источниках Цикла Выполнения Конфигурирования. После выполнения части общей суммы работы поток выполняет цикл выполнения кратко, чтобы видеть, поступило ли сообщение во входной источник. В противном случае цикл выполнения сразу выходит, и цикл продолжает следующий блок работы. Поскольку обработчик не имеет прямого доступа к exitNow локальная переменная, условие выхода передается через пару ключ/значение в словаре потока.

  Проверка перечисления 2-3 условие выхода во время длинного задания

- (void)threadMainRoutine
{
    BOOL moreWorkToDo = YES;
    BOOL exitNow = NO;
    NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
 
    // Add the exitNow BOOL to the thread dictionary.
    NSMutableDictionary* threadDict = [[NSThread currentThread] threadDictionary];
    [threadDict setValue:[NSNumber numberWithBool:exitNow] forKey:@"ThreadShouldExitNow"];
 
    // Install an input source.
    [self myInstallCustomInputSource];
 
    while (moreWorkToDo && !exitNow)
    {
        // Do one chunk of a larger body of work here.
        // Change the value of the moreWorkToDo Boolean when done.
 
        // Run the run loop but timeout immediately if the input source isn't waiting to fire.
        [runLoop runUntilDate:[NSDate date]];
 
        // Check to see if an input source handler changed the exitNow value.
        exitNow = [[threadDict valueForKey:@"ThreadShouldExitNow"] boolValue];
    }
}