Можно ли проверить, заблокирован ли NSThread?
Мне всегда было интересно, как написать следующий код, чтобы использовать его для модульного тестирования:
Можно ли расширить NSThread с помощью метода, который бы проверял, заблокирован ли какой-либо поток?
Сейчас я работаю с NSCondition: Xcode показывает мне цепочку, вызываемую -wait, чтобы заблокировать поток:
[NSCondition wait]
pthread_cond_wait$UNIX2003
_pthread_cond_wait
__psynch_cvwait
Помимо проверки блокировок, выполняемых NSCondition, если это даже возможно, я бы очень признателен за то, что метод работает и для любых других возможностей блокировки (отправка семафоров, блокировок условий, спаренных потоков и т.д.). Я понятия не имею о Objective-C, если возможно, они могут быть схвачены одним методом или каждый из них нуждается в своих собственных.
Вот простой пример того, чего я хотел бы достичь. Таинственный метод называется isBlocked.
// Some test case
// ...
__block NSThread *thread;
NSCondition *condition = [NSCondition alloc] init];
dispatch_async(someQueue(), ^{
thread = NSThread.currentThread;
[condition lock];
[condition wait];
[condition unlock];
});
while(1) {
NSLog(@"Thread is blocked: %d", thread.isBlocked);
}
Примечание: Я плохо разбираюсь в C и всех этих низкоуровневых POSIX-материалах, поэтому, пожалуйста, будьте подробными.
Примечание 2:. Я также заинтересован в решениях, работающих с диспетчерскими очередями: если кто-то может показать мне, как проверить факт, что someQueue() заблокирован - [NSCondition wait] (а не факт, что он будет заблокирован (fx взломает какой-то код раньше - [условие wait] запущено, и блок установлен), но тот факт, что поток/очередь заблокирован), я соглашусь с этим как ответ, как я бы сделал с работой - [NSThread isBlocked].
Примечание 3: Подозревая плохие новости, такие как "это невозможно", я утверждаю, что любые идеи о том, чтобы поймать факт, что - [условие ожидания], были выполнены, и поток был заблокирован (см. Примечание 2) оцениваются и могут быть также приняты в качестве ответа!
ОБНОВЛЕНИЕ 1 в адресе к приятному ответу Ричарда Дж. Росса III. К сожалению, его ответ не работает в моем оригинальном примере, версия, которая ближе к моей реальной работе (хотя она не сильно отличается от примера, который я изначально предоставил, извините, что я не включил его в первый выпуск вопрос):
// Example
// Here I've bootstrapped Richard isLocking categories for both NSThread and NSCondition
// ...
// somewhere in SenTesting test case...
__block NSThread *thread;
NSCondition *condition = [NSCondition alloc] init];
__block BOOL wePassedBlocking = NO;
dispatch_async(someQueue(), ^{
thread = NSThread.currentThread;
[condition lock];
[condition wait];
[condition unlock];
wePassedBlocking = YES; // (*) This line is occasionally never reached!
});
while(!thread.isWaitingOnCondition); // I want this loop to exit after the condition really locks someQueue() and _thread_ __.
// sleep(1);
[condition lock];
[condition broadcast]; // BUT SOMETIMES this line is called before -[condition wait] is called inside someQueue() so the entire test case becomes blocked!
[condition unlock];
while(!wePassedBlocking); // (*) And so this loop occasionally never ends!
Если я раскомментирую sleep (1), тест начинает работать очень стабильно без каких-либо случайных блокировок!
Это приводит нас к проблеме, что категория Ричарда устанавливает состояние ровно на одну строку до фактической блокировки, что означает, что основной поток иногда отслеживает это новое состояние прежде чем мы действительно заблокируем someQueue/thread, потому что код Richard не содержит механизмов синхронизации: @synchronized, NSLock или что-то в этом роде! Надеюсь, я даю ясное объяснение этому сложному делу. Для тех, кто сомневается в том, что я здесь разместил, я бы сказал, что я также экспериментировал с несколькими очередями и даже более сложными делами, и при необходимости я готов предоставить больше примеров. Ричард, еще раз спасибо за ваши усилия, подумайте больше, если вы поймете эти мои моменты!
ОБНОВЛЕНИЕ 2
Я вижу парадокс тупика: очевидно, чтобы действительно установить состояние waitingOnCondition, нам нужно обернуть это изменение состояния внутри некоторых закрытий синхронизации, но проблема в том, что закрытие, разблокировка блокировка синхронизации, должна вызываться после - [условие wait], но она не может, потому что поток уже заблокирован. Опять же, я надеюсь, что описываю это довольно ясно.
Ответы
Ответ 1
Здесь вы идете! Он не будет обнаруживать, что нить ждет от чего-либо другого, кроме -[NSCondition wait]
, но его можно легко расширить, чтобы обнаружить другие виды ожидания.
Вероятно, это не лучшая реализация, но на самом деле она работает и будет делать то, что вам нужно.
#import <objc/runtime.h>
@implementation NSThread(isLocking)
static int waiting_condition_key;
-(BOOL) isWaitingOnCondition {
// here, we sleep for a microsecond (1 millionth of a second) so that the
// other thread can catch up, and actually call 'wait'. This time
// interval is so small that you will never notice it in an actual
// application, it just here because of how multithreaded
// applications work.
usleep(1);
BOOL val = [objc_getAssociatedObject(self, &waiting_condition_key) boolValue];
// sleep before and after so it works on both edges
usleep(1);
return val;
}
-(void) setIsWaitingOnCondition:(BOOL) value {
objc_setAssociatedObject(self, &waiting_condition_key, @(value), OBJC_ASSOCIATION_RETAIN);
}
@end
@implementation NSCondition(isLocking)
+(void) load {
Method old = class_getInstanceMethod(self, @selector(wait));
Method new = class_getInstanceMethod(self, @selector(_wait));
method_exchangeImplementations(old, new);
}
-(void) _wait {
// this is the replacement for the original wait method
[[NSThread currentThread] setIsWaitingOnCondition:YES];
// call the original implementation, which now resides in the same name as this method
[self _wait];
[[NSThread currentThread] setIsWaitingOnCondition:NO];
}
@end
int main()
{
__block NSCondition *condition = [NSCondition new];
NSThread *otherThread = [[NSThread alloc] initWithTarget:^{
NSLog(@"Thread started");
[condition lock];
[condition wait];
[condition unlock];
NSLog(@"Thread ended");
} selector:@selector(invoke) object:nil];
[otherThread start];
while (![otherThread isWaitingOnCondition]);
[condition lock];
[condition signal];
[condition unlock];
NSLog(@"%i", [otherThread isWaitingOnCondition]);
}
Вывод:
2013-03-20 10:43:01.422 TestProj[11354:1803] Thread started
2013-03-20 10:43:01.424 TestProj[11354:1803] Thread ended
2013-03-20 10:43:01.425 TestProj[11354:303] 0
Ответ 2
Вот решение, использующее dispatch_semaphore_t
PGFoo.h
#import <Foundation/Foundation.h>
@interface PGFoo : NSObject
- (void)longRunningAsynchronousMethod:(void (^)(NSInteger result))completion;
@end
PGFoo.m
#import "PGFoo.h"
@implementation PGFoo
- (void)longRunningAsynchronousMethod:(void (^)(NSInteger))completion {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(5);
completion(1);
});
}
@end
Методы испытаний
- (void)testThatFailsBecauseItIsImpatient {
PGFoo *foo = [[PGFoo alloc] init];
__block NSInteger theResult = 0;
[foo longRunningAsynchronousMethod:^(NSInteger result) {
theResult = result;
}];
STAssertEquals(theResult, 1, nil);
}
- (void)testThatPassesBecauseItIsPatient {
PGFoo *foo = [[PGFoo alloc] init];
__block NSInteger theResult = 0;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[foo longRunningAsynchronousMethod:^(NSInteger result) {
theResult = result;
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
STAssertEquals(theResult, 1, nil);
}
Используя dispatch_semaphore_t
, вы можете "отслеживать", заблокирован ли поток, ожидающий этого семафора. Для каждого вызова dispatch_semaphore_wait
счет семафора уменьшается и поток ожидает, пока не будет вызван вызов dispatch_semaphore_signal
, когда вызывается dispatch_semaphore_signal
, счет семафора увеличивается, если счет увеличивается до значения, превышающего -1
поток продолжается.
Это решение не может ответить на ваш вопрос о проверке того, заблокирован ли NSThread
, но я думаю, что он обеспечивает то, к чему вы достигаете, если вы не дойдете до проверки экземпляров NSThread
, которые поддерживаются в пределах существующей структуры.