Многопоточный вопрос в Objective-C 2.0
У меня есть основной делегат приложения, который содержит метод, который возвращает объект. Этот делегат приложения работает в главном потоке.
У меня также есть NSOperation, который запускается в другом потоке. Помимо желания иногда вызывать мой метод делегата приложения в моем основном потоке, мне также нужно вызывать его из потока NSOperation для получения возвращаемого объекта. Мой первый вопрос, если я позвоню из другой ветки...
id newObject = [[[UIApplication sharedApplication] delegate] myMethod];
... будет ли этот метод обрабатываться в том же потоке, что и операция NSOperation, или это будет тот же поток (основной), что и делегат приложения?
Я также хочу убедиться, что код внутри myMethod
вызывается только один раз за раз моим потоком операций или моим основным потоком. Могу ли я просто создать экземпляр NSLock var в моем делегате приложения и сделать что-то вроде:
-(id)myMethod {
[myLock lock];
myObject = // Get or create my object to return
[myLock unlock];
return myObject;
}
Спасибо за вашу помощь!
Майк
3 ответа
Если вы явно не напишите код, который вызовет выполнение чего-либо в другом потоке, каждый вызов метода будет выполняться непосредственно в потоке, к которому он был вызван. В вызове метода нет магии. Вы можете думать об этом как о наличии той же семантики /ABI, что и при вызове функции C, для целей многопоточности.
Ваш шаблон блокировки будет работать нормально, чтобы обеспечить эксклюзивный доступ между потоками.
Две дополнительные несвязанные заметки (потому что так много людей спотыкаются):
объявление имущества как
atomic
имеет мало общего с безопасностью потоков. Атомность только гарантирует, что вы получите правильное значение, а не правильное значение (есть разница).Автоматически освобожденные объекты никогда не могут безопасно передаваться между потоками. Вам нужен явный
retain
в потоке отправки и балансировки возможноrelease
на принимающей ветке.
Вам обязательно нужно выполнить этот вызов на NSOperation
поток, а не просто предоставить требуемый объект как часть создания пользовательской операции?
Если это так, я бы рекомендовал не использовать блокировки, если производительность не является критической. Если iPhone поддерживает это, вы можете использовать Grand Central Dispatch, чтобы получить объект в ваш поток:
__block id newObject = nil;
dispatch_sync(dispatch_get_main_queue(), ^{
newObject = [[[[UIApplication sharedApplication] delegate] myMethod] retain];
});
Для iPhone я бы хотел создать вспомогательный метод:
- (void)createNewObject:(NSValue *)returnPtr {
id newObject = [[[[UIApplication sharedApplication] delegate] myMethod] retain];
*(id *)[returnPtr pointerValue] = newObject;
}
И вызвать это так от вашего NSOperation
нить:
id newObject = nil;
[self performSelectorOnMainThread:@selector(createNewObject:)
withObject:[NSValue valueWithPointer:&newObject]
waitUntilDone:YES];
На самом деле выполнение выполнения в главном потоке имеет меньше неявных рисков.
Если вам просто нужно защитить критическую часть кода, почему бы не использовать директиву Objective-C @synchronized? Конечно, использование NSLock также будет работать, но вам нужно явно управлять экземпляром NSLock. Из документации:
Objective-C поддерживает многопоточность в приложениях. Это означает, что два потока могут пытаться изменить один и тот же объект одновременно, что может вызвать серьезные проблемы в программе. Чтобы защитить разделы кода от одновременного выполнения несколькими потоками, Objective-C предоставляет директиву @synchronized().
Директива @synchronized() блокирует часть кода для использования одним потоком. Другие потоки блокируются, пока поток не выйдет из защищенного кода; то есть, когда выполнение продолжается после последнего оператора в блоке @synchronized().
Директива @synchronized() принимает в качестве единственного аргумента любой объект Objective-C, включая self. Этот объект известен как семафор взаимного исключения или мьютекс. Это позволяет потоку заблокировать часть кода, чтобы предотвратить его использование другими потоками. Вы должны использовать отдельные семафоры для защиты различных критических разделов программы. Безопаснее всего создавать все объекты взаимного исключения, прежде чем приложение станет многопоточным, чтобы избежать состояния гонки.
В листинге 12-1 показан пример кода, который использует self в качестве мьютекса для синхронизации доступа к методам экземпляра текущего объекта. Вы можете использовать аналогичный подход для синхронизации методов класса связанного класса, используя объект Class вместо self. В последнем случае, конечно, только одному потоку одновременно разрешено выполнять метод класса, потому что существует только один объект класса, который является общим для всех вызывающих.
Перечисление 12-1, Блокирующее метод, используя self
- (void)criticalMethod
{
@synchronized(self) {
// Critical code.
...
}
}