Задача Санта-Клауса и практическая логистика

в 17:27, , рубрики: iOS SDK, objective-c, xcode, Алгоритмы, параллельные вычисления, потоки, Программирование, процессы, разработка под iOS, метки: , , , , , ,

image
Известно, что только 5% программистов в состоянии решать задачи многопоточного программирования. А в месте с тем, с ростом количество ядер даже у мобильных устройств потребность в использовании нескольких нитей возрастает многократно. С каждым днем появляются как новые языки программирования, специально предназначенные для решения специфических задач параллельного программирования, так и в уже хорошо известных и широко применяемых решениях появляются методы, которые не только облегчают понимание, но и сводят решение задачи к своеобразной поэзии программного кода.

image
Читая книгу “Идеальный код” под редакцией Энди Орама и Грега Уилсона мне довелось натолкнуться на интереснейшую задачу в главе посвященной параллельной обработке (гл. 24. стр. 444). В ней автор, Саймон Пейтон Джоунс, приводит решение на языке Haskell. Там же он утверждает, что существуют решения задачи Сата Клауса для языков Ada95 и Polyphonic C#. В силу профессиональных интересов несколько ранее мне приходилось обсуждать с коллегами возможности многопоточной Apple реализации для языка Objective-C.

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

Постановка задачи:

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

Вступление:

Мне показалось, что это идеальная задача продемонстрировать именно возможности языка. В свое время мне довелось много программировать на C# многопоточные приложения, и тогда я думал, что языка совершенней для этой цели нет. С# подкупал своей простотой и эффективностью. Но Apple превратила этот процесс в песню. И как в каждой песне нужно почувствовать тот ритм, тот метр, который подарит наслаждение от услышанной музыки. По началу разобраться с нагромождением скобочной записи — задача нетривиальная, предназначенная для людей с развитой усидчивостью и внимательностью.

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

Весь код написан с испоьзованием ARC, в XCode 4.3. Это уточнение необходимо, так как, во-первых, XCode 4.3. научился видеть методы, даже если они описаны после их использования, а во-вторых, ARC позволяет не заботится об уничтожении объектов и особой диспетчеризации памяти. Т.е. объекты не только не нужно освобождать, то и попросту это запрещено компилятором. Выигрыш существенный — и в прозрачности кода, и в скорости выполнения, и, безусловно во времени затрачиваемом на отладку.

Вначале опишем соответствующие интерфейсы:
@interface Persone : NSObject
{
NSInteger number;
}
@property (nonatomic, copy) NSString *name; // Имя типа персоны.
- (Persone *)execution;
@end

#import "Common.h"
@interface Elf : Persone
@end

#import "Common.h"
@interface Deer : Persone
@end

Как видим. все предельно просто и не вызывает каких-либо дополнительных вопросов.

С интерфейсом Санты все интересней.
#import "Common.h"
@interface Santa : Persone
@property (nonatomic) BOOL isBusy; // Санта занят? Да Нет.
- (Persone *)wakeUp:(NSArray *)group; //Тук. тук, тук. Новая группа хочет аудиенции. Санта. просыпайся!
@end

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

В каждом из интерфейсов присутствует загадочный файл который может вызвать недоумение:

#import «Common.h» Но с ним все просто — он описывает значение констант используемых в программе, а так же некоторые другие заголовочные файлы:

#import "Persone.h"
#import "Deer.h"
#import "Elf.h"
#import "Santa.h"
#import "Groups.h"

#define kAllMessages 1 // Если раскомментировать эту строчку, в лог будет выводится все сообщения.
#define kGroupDeers @"Deer" // Ключ для очереди Оленей.
#define kGroupElfs @"Elf" // Ключ для очереди Эльфов.
#define kMontsDeer 12 // Диапазон месяцев для выпаса Оленей 1-12.
#define kMontsElf 18 // Эстимейт работы Эльфов в мес. 1-18.
#define kMontsSanta 4 // Длительность рабочих совещаний Сенты.
#define countElfInGroup 3 // Количество членов в группе Эльфов, для получения аудиенции Санты.
#define countDeerInGroup 9 // Количество членов в группе Оленей, для получения аудиенции Санты.

Остался последний значащий интерфейс, который предназначен для сведений персон одного вида в группы:
#import "Common.h"
@interface Groups : NSObject
{
NSMutableDictionary *groups; // Группы очередей.
}
- (Groups *)add:(Persone *)persone; // Ставим персону в соответствующую очередь.
- (NSArray *)ready; // Извлекаем допустимую группу.
@end

Реализация:

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

Начнем с простого… Вначале создадим Оленя.
#import "Common.h"
@implementation Deer
static int count;
@synthesize name;
- (id)init
{
if(count >=9) return nil; // Оленей не может быть больше 9.
self = [super init];
if (self) {
count++;
number = count;
self.name = kGroupDeers;
}
return self;
}

Инициализатор вполне стереотипный и не нуждается в каких либо пояснениях. В суперклассе присутствует свойство name, которым мы воспользуется позже, в виде переменной содержащей значения ключа для словаря.
- (void)eat
{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSInteger month = (arc4random() % kMontsDeer) + 1;
#if kAllMessages
NSLog(@"%d-й олень ушел на выпас на %d месяцев", number, month);
#endif
[NSThread sleepForTimeInterval:month];
#if kAllMessages
NSLog(@"%d-й олень вернулся с пастбища", number);
#endif
dispatch_async(dispatch_get_main_queue(), ^{
[[[Groups alloc] init] add:self];
});
});
}

А вот здесь уже поинтересней. Метод eat запускает отдельный поток выполнения, для текущего экземпляра Оленя. Т.е каждый Олень, а в дальнейшем и Эльфы у нас будут жить и здравствовать в отдельных потоках. Не смотря на сложную скобочную конструкцию, в действительности, здесь все предельно просто: dispatch_async(dispatch_get_global_queue(0, 0), ^{}); — ставит то, что описано в скобках в очередь выполнения. Аргумент dispatch_get_global_queue означает глобальную очередь. (0, 0) — первый 0 обозначает приоритет, по-умолчанию, Normal, второй 0 вообще ничего не обозначает в iOS SDK, и зарезервирован под дальнейшее развитие технологии.

Внутри описанного блока мы находим: NSInteger month = (arc4random() % kMontsDeer) + 1; — получение случайного числа месяцев выпаски Оленя. Получаемый в этой формуле диапазон целых чисел от 1 до kMontsDeer. Далее следует задержка выполнения процесса — своеобразная эмуляция пастбища — [NSThread sleepForTimeInterval:month]; И завершает этот метод добавление в аналогичную очередь но уже для главного потока некоторого кода. Этот некоторый код представляет собой еще одну очередь, но уже созданную на уровне текущей программы. Как мы помним, диспетчер групп у нас должен быть один и только один на все приложение. Другими словами — он является синглтоном. Поэтому, создание его экземпляра через инициализатор, в действительности вернет нам уже существующий экземпляр, если он был кем-то ранее создан. Метод add просто добавит текущего Оленя в очередь “на прием”.
- (Persone *)execution
{
[self eat];
return self;
}
@end

Последний метод всего лишь обеспечивает согласование базовых интерфейсов. Т.е. создает шаблонный метод execute.

Идентичным образом мы создаем и класс для эльфов.

#import "Common.h"
@implementation Elf
static int count;
@synthesize name;
- (id)init
{
if(count >=10) return nil; // Эльфов не может быть больше 10.
self = [super init];
if (self) {
count++;
number = count;
self.name = kGroupElfs;
}
return self;
}

- (void)work
{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSInteger month = (arc4random() % kMontsElf) + 1;
#if kAllMessages
NSLog(@"Приступил к работе %d эльф. Будет занят %d месяцев", number, month);
#endif
[NSThread sleepForTimeInterval:month];
#if kAllMessages
NSLog(@"Закончил работу %d эльф", number);
#endif
dispatch_async(dispatch_get_main_queue(), ^{
[[[Groups alloc] init] add:self];
});
});
}

- (Persone *)execution
{
[self work];
return self;
}
@end

Разделяй и влавстуй

Задачей класса Group является диспетчеризация и формирование групп. Механизм довольно простой. Каждую из персон ставят в очередь для своего типа, (т.е. Эльфов к эльфам, Оленя к оленям), а затем, если в очереди набирается нужное количество экземпляров, из оттуда извлекают и помещают в специально предназначенный для них буфер — приемный покой Санта-Клауса.
#import "Common.h"
@implementation Groups
static Groups *instance;

- (id)init
{
if(instance!=nil) return instance;
self = [super init];
if (self) {
groups = [NSMutableDictionary dictionary];
instance = self;
}
return self;
}
- (Groups *)add:(Persone *)persone
{
NSMutableArray *queue = [groups objectForKey:persone.name]; // Получаем очередь для типа персоны.
if(queue == nil)
queue = [NSMutableArray array];
[queue addObject:persone]; // Ставим персону в очередь.
[groups setValue:queue forKey:persone.name];
#if kAllMessages
NSLog(@"В очереди "%@" персон %d", persone.name, queue.count);
#endif
Santa *santa = [[Santa alloc] init];
if(santa.isBusy == NO) [santa wakeUp:[self ready]];
return self;
}

- (NSArray *)queueElf
{
NSMutableArray *queue = [groups objectForKey:kGroupElfs];
if(queue.count < countElfInGroup) return nil; // Группа не может быть собрана.
NSMutableArray *group = [NSMutableArray array];
for (int i=0; i<countElfInGroup; i++) // 3 Эльфа покидают очередь ...
[group addObject:[queue objectAtIndex:i]];
for (id item in group) // ... и образуют рабочую очередь.
[queue removeObject:item];
[groups setValue:queue forKey:kGroupElfs];
return [group copy];
}

- (NSArray *)queueDeer
{
NSMutableArray *queue = [groups objectForKey:kGroupDeers];
if(queue.count < countDeerInGroup) return nil; // Группа не может быть собрана.
NSArray *group = [queue copy];
for (id item in group) // Олени покидают очередь.
[queue removeObject:item];
[groups setValue:queue forKey:kGroupDeers];
return group;
}

- (NSArray *)ready
{
NSArray *group = [self queueElf];
if(group!=nil) return group;
return [self queueDeer];
}

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

Самым интересным местом этого кода является следующий участок:

Santa *santa = [[Santa alloc] init];
if(santa.isBusy == NO) [santa wakeUp:[self ready]];

Мы как и с диспетчером группы получаем экземпляр нашего Санты, а затем будем его, запихивая в его покои образовавшуюся группу. Все равно какую. За то будут это Олени или Эльфы отвечает метод — (NSArray *)ready

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

- (NSArray *)ready
{
NSArray *group = [self queueDeer];
if(group!=nil) return group;
return [self queueElf];
}

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

Мадригал

И вот мы добрались до заключительного персонажа нашей песни — Санты:
#import "Common.h"
@implementation Santa
static Santa *santa;
static int count;
BOOL doWork;
@synthesize isBusy;
static int groupNumber;

- (id)init
{
if(santa!=nil) return santa;
self = [super init];
if (self) {
santa = self;
count++;
number = count;
NSLog(@">> Родился %d-й Санта", number);
}
return self;
}

- (void)sleep
{
NSLog(@">> Санта %d засыпает....", number);
}

- (Santa *)wakeUp:(NSArray *)group
{
if(group == nil) return self;

NSLog(@">> Санта %d просыпается.... ", number);
[self work:group]; // Санта приступил к работе.
return self;
}

- (BOOL)isBusy
{
return doWork;
}

- (void)work:(NSArray *)group
{
if(doWork == YES) return;
doWork = YES;
__block Santa *santa = self;
__block NSArray *currentGroup = group;
__block NSInteger IDGroup = ++groupNumber;
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSInteger month = (arc4random() % kMontsSanta) + 1;
NSLog(@">> Приступил к работе %d-й Санта. Закончит через %d месяцев c группой ID %d", number, month, IDGroup);
[santa portalIn:currentGroup];
[NSThread sleepForTimeInterval:month];
NSLog(@">> Санта %d закончил работу c группой ID %d", number, IDGroup);
dispatch_async(dispatch_get_main_queue(), ^{
doWork = NO;
[[self portalOut:currentGroup] sleep]; // Закончили работать и попытались уснуть.
});
});
}

- (Persone *)execution
{
NSLog(@"Yahoo!!!");
return self;
}

- (Santa *)portalIn:(NSArray *)group
{
if([[group objectAtIndex:0] isKindOfClass:[Elf class]])
NSLog(@">> Санта %d работает с группой эльфов", number);
if([[group objectAtIndex:0] isKindOfClass:[Deer class]])
NSLog(@"nttМы поедем мы помчимся на оленях yтpом pаннимnttИ отчаянно воpвемся пpямо в снежнyю заpю.nttТы yзнаешь, что напpасно называют севеp кpайним,nttТы yвидишь он бескpайний, я тебе его даpю");
return self;
}
- (Santa *)portalOut:(NSArray *)group
{
for (Persone *persone in group)
[persone execution];
return self;
}

Прежде чем перейти к наиболее сложной части кода необходимо вскользь остановится на двух методах:

метод входных дверей: portalIn:(NSArray *)group и метод выходных дверей: portalOut:(NSArray *)group.

Можно было бы слегкостью обойтись без них, но, таково было исходное условие задачи — акцентировать внимание на входных и выходных дверях.

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

Метод wakeUp:(NSArray *)group впускает группу в приемные покои и будит Санту. Все основное происходит в методе work.

Вначале мы проверяем, не занят ли Санта. if(doWork == YES) return; Это избыточная проверка, и ее можно удалить. Но, лучше мы пропустим одну побудку, чем что-то пойдет не по плану. Затем мы вывешиваем табличку “занято” и запираем входные двери: doWork = YES;

Далее, мы должны подготовить некоторые переменные для использования их внутри потоков, фонового и главного:
__block Santa *santa = self;
__block NSArray *currentGroup = group;
__block NSInteger IDGroup = ++groupNumber;

Если этого не сделать, может возникнуть исключение доступа к неразделяемым ресурсам. Выше приведенная простая манипуляция гарантирует то, что это не произойдет. Здесь следует отметить, что в ключевом слове __block используется двойное подчеркивание. т.е. символ _ используется дважды! Затем мы создаем для Санты отдельный поток, как это делали с Оленями и Эльфами, и выпускаем пришедшую группу в покои Санты методом входных дверей: [santa portalIn:currentGroup];

В зависимости от того, кто к Санте пришел, он делает одно из двух действий — либо проводит планерку, либо развозит подарки. Когда же работа завершается, Санта отпирает дверь, и выпроваживает гостей на прежнее место работы. Вся магия задачи заключена в методе выходных дверей. Как известно, “Итерация свойственна человеку, рекурсия божественна”. — L. Peter Deutsch. Поскольку данный метод вызывается в рамках главного потока, то он может безопасно рекурсивно вызвать выполнение новой задачи персоной. Эльф или Олень, будучи рожденными однажды, будет бесконечно долго выполнять свои обязанности, пока не будут остановлены прямым выстрелом в голову из SIGABRT.

А вот теперь пришло время песни.

В удобном месте необходимо расположить код создания наших персон. У меня он расположен в — (void)viewDidLoad

Куплет 1.

for (int i = 0; i<9; i++) [[[Deer alloc] init] execution];
for (int i = 0; i<10; i++) [[[Elf alloc] init] execution];
for (int i = 0; i<4; i++) [[[Santa alloc] init] execution];

Но так как мы уже съели собаку на создании потоков, то почему бы не обернуть все это в отдельный поток?

Куплет 2.

dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i<9; i++) [[[Deer alloc] init] execution];
for (int i = 0; i<10; i++) [[[Elf alloc] init] execution];
for (int i = 0; i<4; i++) [[[Santa alloc] init] execution];
});

Вполне очевидно, что так каждая персона живет в отдельном потоке, то последовательность создания каждой персоны будет определена лишь волей случая. Что же делать? Специально для этого имеется очень элегантный способ указать на последовательность создания потоков:

Куплет 3.

dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{for (int i = 0; i<30; i++) [[[Deer alloc] init] execution];});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{for (int i = 0; i<30; i++) [[[Elf alloc] init] execution];});
dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{for (int i = 0; i<10; i++) [[[Santa alloc] init] execution];});

});

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

Если в первых случаях протокол будет примерно такой:
>> Родился 1-й Санта
1-й олень ушел на выпас на 6 месяцев
2-й олень ушел на выпас на 7 месяцев
3-й олень ушел на выпас на 9 месяцев
4-й олень ушел на выпас на 4 месяцев
5-й олень ушел на выпас на 3 месяцев
Yahoo!!!
7-й олень ушел на выпас на 5 месяцев
9-й олень ушел на выпас на 2 месяцев
Yahoo!!!
Yahoo!!!
Yahoo!!!
6-й олень ушел на выпас на 8 месяцев
Приступил к работе 3 эльф. Будет занят 15 месяцев
Приступил к работе 5 эльф. Будет занят 17 месяцев
Приступил к работе 4 эльф. Будет занят 10 месяцев
Приступил к работе 2 эльф. Будет занят 16 месяцев
8-й олень ушел на выпас на 3 месяцев

То в последнем случае он будет примерно такой:

1-й олень ушел на выпас на 7 месяцев
4-й олень ушел на выпас на 5 месяцев
3-й олень ушел на выпас на 12 месяцев
2-й олень ушел на выпас на 7 месяцев
5-й олень ушел на выпас на 3 месяцев
6-й олень ушел на выпас на 4 месяцев
7-й олень ушел на выпас на 7 месяцев
8-й олень ушел на выпас на 11 месяцев
9-й олень ушел на выпас на 6 месяцев
Приступил к работе 1 эльф. Будет занят 11 месяцев
Приступил к работе 2 эльф. Будет занят 2 месяцев
Приступил к работе 3 эльф. Будет занят 16 месяцев
Приступил к работе 4 эльф. Будет занят 4 месяцев
Приступил к работе 5 эльф. Будет занят 16 месяцев
Приступил к работе 6 эльф. Будет занят 1 месяцев
Приступил к работе 7 эльф. Будет занят 2 месяцев
Приступил к работе 8 эльф. Будет занят 14 месяцев
Приступил к работе 9 эльф. Будет занят 15 месяцев
Приступил к работе 10 эльф. Будет занят 3 месяцев
>> Родился 1-й Санта
Yahoo!!!
Yahoo!!!
Yahoo!!!
Yahoo!!!
Yahoo!!!
Yahoo!!!
Yahoo!!!
Yahoo!!!
Yahoo!!!
Yahoo!!!
Закончил работу 6 эльф
В очереди "Elf" персон 1
Закончил работу 2 эльф
В очереди "Elf" персон 2

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

image
Что может быть интересного в логах? Дело в том, что разное значение исходных констант, обеспечивает совершенно разное поведение персонажей. К примеру, во время начальной отладки я обнаружил, что Санта может никогда не начать развозить подарки, так как будет постоянно занят рутиной совещаний. После длительных и безрезультатных поисков ошибки в коде, было обнаружено, что все укладывается в рамках стандартной статистической модели: если длительность выполнения задачи у Эльфа слишком короткое (3-4) месяца, то группы будут формироваться настолько часто, что Олени будут ожидать бесконечно долго в очереди (напомню, что задача была решена с инверсией условия).

Какова практическая ценность данного упражнения с точки зрения логистики? Предположим, что Вы являетесь менеджером проекта, работающего по Scrum. Так как Вам надлежит ежедневно проводить совещания, временами переходящие в пленарные заседания Верховного Совета СССР, то может статься так, что времени на разработку у Эльфов не останется, и проект перейдет в стадию постоянного цейтнота. Аналогичная ситуация может сложится в теугольнике тим-лид — серверные разработчика — клиентские разработчики, с той лишь разницей, что как правило, тим-лид перестает выполнять свои непосредственные обязанности (спать — sleep();), а вынужден бесконечно разгребать код Оленей или злобных Эльфов. А если оторваться от сферы IT, то коэффициенты задачи Санты, являются основополагающими при процессах погрузки разгрузки складирования товаров. В общем, достаточно легко найти аналоги ей в реальной жизни.

Заключение:

В приведенном коде встречаются некоторые не вполне очевидные приемы о которых нужно сказать отдельно.

1)

NSMutableArray *group = [NSMutableArray array];
[skip]
return [group copy];

Программисты которые до сих пор работают без ARC придут в ужас от такого кода. И, скорее всего, они будут использовать что-то вроде [[NSArray arrayWithArray:group] autorelise]; для схлопывания объекта до минимального функционала. Однако, в ARC такая необходимость отпадает, а код становится более прозрачным.

2)

- (Santa *)portalOut:(NSArray *)group
{
[skip]
return self;
}

Метод возвращающий текущий объект позволяет сократить листинг, благодаря использованию прискобочной записи objective-c: [self portalOut:currentGroup] sleep]; Для любителей функциональных языков и тех кто любит расширяемые методы C# (кои активно используются в LINQ) это как бальзам на душу… Конечно, в целях отладки имеет смысл разбивать вызов методов на две строки.

3)

#if kAllMessages
NSLog(@"Приступил к работе %d эльф. Будет занят %d месяцев", number, month);
#endif

Директива прекомпиляции kAllMessages содержится в файле Common.h Если ее раскомментировать, то будут видны все действия, выполняемые персонажами, как в приведенных выше логах. По-умолчанию в консоль выводятся только действия Санты. Я не стал в статье останавливаться на консольный вывод, так как, обычно, это первое с чем начинается знакомство при изучении objective-c.

Категорический императив:

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

Ссылки:

Исходный код приложения (iOS SDK 5, XCode 4.3).
Лог приложения без групп выполнения потоков.
Лог приложений с группой выполнения потоков.
Сокращенный лог работы Санты в группе потока.

Резюме:

В статье рассмотрен механизм создания многопоточного приложения на языке objective-C применительно к iOS SDK 3+. В качестве примера была приведена задача Санты из области логистики. Исходный код демонстрирует использования асинхронного диспетчера dispatch_async, очередей главного потока приложения dispatch_get_main_queue(), фонового потока приложения dispatch_get_global_queue, и интервала задержки [NSThread sleepForTimeInterval:month];. Для создания дополнительных очередей были использованы линейные массивы и динамический словарь, предоставляемые Objective-C.

Автор: Demtriy


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js