是否可以检查NSThread是否被阻止?

时间:2013-03-18 01:30:51

标签: objective-c locking pthreads

我一直对如何编写以下代码以将其用于单元测试感兴趣:

是否可以使用检查特定线程是否被阻止的方法来扩展NSThread?

现在我正在使用NSCondition:Xcode向我展示了由 -wait 调用以阻止线程的链:

[NSCondition wait] 
pthread_cond_wait$UNIX2003
_pthread_cond_wait
__psynch_cvwait

除了检查NSCondition完成的锁之外,如果它甚至可能,我也非常感谢方法也适用于任何其他阻塞功能(调度信号量,条件锁,睡眠线程等) - 我不知道目标-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等待]阻止的事实(不是事实上它将被阻止(fx黑客攻击某些代码 - [条件等待]运行并且阻止设置),但是线程/队列被阻止的事实),我会接受这个作为答案,就像我对工作一样 - [NSThread isBlocked]方法。

注意3:怀疑“这是不可能的”这样的坏消息,我声称任何有关捕捉 - [条件等待]运行且线程设置被阻止这一事实的想法(请参阅注释) 2)表示赞赏,也可以作为答案接受!

更新1 ,以解决Richard J. Ross III的不错答案。不幸的是,他的答案在我原来的例子中不起作用,这个版本更接近我的实际工作(虽然它与我最初提供的例子没有太大差别 - 抱歉我没有把它包含在第一版的问题):

// Example

// Here I've bootstrapped Richard's 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 的状态,我们需要在一些同步闭包中包装这个状态的变化,但问题是关闭一个,解锁同步锁,应该在 - [条件等待]之后调用,但它不能,因为线程已被阻塞。我再次希望我能够非常清楚地描述它。

2 个答案:

答案 0 :(得分:4)

你走了!它不会检测除-[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's 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

答案 1 :(得分:0)

以下是使用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个实例在现有框架内维护。