【问题标题】:Is it possible to check if an NSThread is blocked?是否可以检查 NSThread 是否被阻塞?
【发布时间】:2013-03-18 01:30:51
【问题描述】:

我一直对如何编写以下代码来进行单元测试很感兴趣:

是否可以使用检查特定线程是否被阻塞的方法来扩展 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 破解一些代码),但 线程/队列 被阻止的事实),我会接受这个作为一个答案,就像我使用 -[NSThread isBlocked] 方法一样。

注 3: 怀疑像“不可能”这样的坏消息,我声称任何关于捕捉 -[condition wait] 已运行并且线程被设置为阻塞的事实的想法(见注2) 表示赞赏,也可以作为答案接受!

UPDATE 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) 测试开始工作非常稳定,没有任何偶然的锁定!

这导致我们遇到问题,Richard 的类别确实在实际阻塞完成前一行设置了状态,这意味着有时测试用例的主线程会捕获这个新状态在我们真正阻止 someQueue/thread 之前,因为 Richard 的代码不包含任何同步机制:@synchronized、NSLock 或类似的东西!我希望我能清楚地解释这个棘手的案例。对于任何对我在这里发布的内容有疑问的人,我想说我也一直在尝试多个队列甚至更复杂的案例,如果需要,我准备提供更多示例。理查德,再次感谢您的努力,如果您理解我的这些观点,让我们一起思考更多!

更新 2

我看到了死胡同:显然,要真正设置 waitingOnCondition 的状态,我们需要将此状态的更改包装在一些同步闭包中,但问题是关闭一个,解锁同步锁,应该在-[条件等待]之后调用,但是不能,因为线程已经被阻塞了。再次,我希望我描述得很清楚。

【问题讨论】:

  • 线程未“锁定”。一个线程可以持有一个锁,或者可能等待一个锁,不过——你的意思是其中之一吗?
  • 对不起,如果我没有使用正确的术语。但我确信我的例子确实描述了我想要的,不是吗? -[condition wait] “停止”线程,直到从其他线程调用 -[condition signal] 的那一刻 - 我想了解条件设置为等待并且线程已停止滚动的事实.
  • @Stanislaw 只需在 NSThread 上写一个类别来处理它。
  • @CodaFi,这就是我在这里要问的问题;)我不知道该怎么做。
  • 我敢说,大多数经验丰富的多线程熟悉的开发人员不会使用,也不需要isBlocked 之类的东西。它最终可能最终会背叛你。

标签: objective-c locking pthreads


【解决方案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'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] 线程开始 2013-03-20 10:43:01.424 TestProj[11354:1803] 线程结束 2013-03-20 10:43:01.425 测试项目 [11354:303] 0

【讨论】:

  • Richard J. Ross III,我很想知道您对我刚刚编辑的问题中的更新 1 和 2 有何看法。
  • @Stanislaw 我看到了你的问题。我已经在...锁上加了一个锁,所以试试看它是否有效。如果没有,我将制作一个自定义互斥锁。
  • 我试过了。如果没有睡眠(1)甚至睡眠(0.1),它就无法工作。我很想看看“自定义互斥锁是什么”。
  • @Stanislaw 如果你用usleep(1) 睡一微秒会发生什么?并且自定义互斥锁是一个可以在这种情况下正确锁定的类,并且不依赖于运行时组件等魔法。
  • usleep(1) 似乎有效!有什么方法可以将它整合到您的类别中?
【解决方案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 时,如果计数增加到大于@987654330 的值,则信号量的计数会增加@线程继续。

此解决方案无法回答您关于检查 NSThread 是否“被阻止”的问题,但我认为它提供了您想要的内容,假设您没有到达检查在现有框架。

【讨论】:

  • dispatch semaphore 的问题是它们使用队列,队列不等同于线程。
  • @abbood 这是正确的。单个线程实际上可以运行多个队列,单个队列几乎相当于while循环生成块。不过,不完全是。
  • @RichardJ.RossIII 哦,我明白了。 Prolly 这就是为什么 GCD 不是实时的好选择。我引用 discussion“[GCD] 不能用于实时音频。它使用池和队列,您提交给它的实时相关工作可以坐下来在队列中等待很长时间,这超出了您的控制范围。最好将您的工作专门用于实时高优先级线程,并使该线程上的工作变得非常简单和快速。”
  • @abbood 这与我读过的类似。我的应用程序从来不需要那种级别的控制,所以我一直非常依赖 GCD 和 NSOperation。当 GCD 满足您的要求时,它在“真实线程”上的简单性/抽象性非常好。
  • @abbood 是的,Apple 建议尽可能使用最高级别的 API,并且当 GCD 出现(iOS 4?)时,NSOperation 被重写为使用 GCD。越走越高总是有限制,所以有时直接使用 GCD 是有意义的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-02-13
  • 2016-12-20
  • 2018-03-19
  • 1970-01-01
  • 1970-01-01
  • 2012-11-30
  • 1970-01-01
相关资源
最近更新 更多