NSOperation& Singleton:正确的concurency设计

时间:2012-07-21 18:38:06

标签: objective-c ios multithreading nsoperation nsoperationqueue

我需要你们在这里设计我的应用程序的建议,基本上我想知道它是否会像我期望的那样工作?由于多线程非常棘手,我想听听你的意见。

基本上我的任务很简单 - 我SomeBigSingletonClass - 大单身类,有两种方法someMethodOnesomeMethodTwo 应定期调用这些方法(基于计时器)和单独的线程。 但是目前每个线程应该只有一个实例,例如任何时候都应该只有一个someMethodOnesomeMethodTwo相同。

我尝试了什么

GCD - 使用GCD实现但是它没有非常重要的功能,它没有提供检查目前是否有任何正在运行的任务的方法,即我无法检查是否有只有一个运行实例让我说someMethodOne方法。

NSThread - 它确实提供了良好的功能,但我很确定NSOperation和GCD等新的高级技术将使维护我的代码变得更加简单。所以我决定放弃NSThread。

我的解决方案NSOperation 我计划如何实现两个线程调用

@implementation SomeBigSingletonClass

- (id)init
{
    ...
    // queue is an iVar
    queue = [[NSOperationQueue alloc] init];

    // As I'll have maximum two running threads 
    [queue setMaxConcurrentOperationCount:2];
    ...
}

+ (SomeBigSingletonClass *)sharedInstance
{
    static SomeBigSingletonClass *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[SomeBigSingletonClass alloc] init];
    });
    return sharedInstance;
}

- (void)someMethodOne
{
    SomeMethodOneOperation *one = [[SomeMethodOneOperation alloc] init];
    [queue addOperation:one];
}

- (void)someMethodTwo
{
    SomeMethodTwoOperation *two = [[SomeMethodOneOperation alloc] init];
    [queue addOperation:two];
}
@end 

最后我的NSOperation继承的类看起来像这样

@implementation SomeMethodOneOperation

- (id)init
{
    if (![super init]) return nil;
    return self;
}

- (void)main {
    // Check if the operation is not running
    if (![self isExecuting]) {
        [[SomeBigSingletonClass sharedInstance] doMethodOneStuff];
    }
}

@end

SomeMethodTwoOperation操作类也一样。

2 个答案:

答案 0 :(得分:2)

如果您使用NSOperation,则可以实现您想要创建自己的NSOperationQueue并将numberOfConcurrentOperations设置为1的目标。

您也可以使用@synchronized范围作为锁定对象。

编辑:澄清---

我的建议是:

队列A(1个并发操作 - 用于一次执行SomeMethodOneOperation SomeMethodTwoOperation

队列B(n个并发操作 - 用于执行一般后台操作)

编辑2:更新的代码说明了运行最大操作1和操作2的方法,在任何给定时间执行操作1和操作2中的每一个最多。

-(void)enqueueMethodOne
{
    static NSOperationQueue * methodOneQueue = nil ;
    static dispatch_once_t onceToken ;    
    dispatch_once(&onceToken, ^{
        queue = [ [ NSOperationQueue alloc ] init ] ;
        queue = 1 ;
    });

    [ queue addOperation:[ NSBlockOperation blockOperationWithBlock:^{
        ... do method one ...
    } ] ];
}

-(void)enqueueMethodTwo
{
    static NSOperationQueue * queue = nil ;
    static dispatch_once_t onceToken ;    
    dispatch_once(&onceToken, ^{
        queue = [ [ NSOperationQueue alloc ] init ] ;
        queue = 1 ;
    });

    [ queue addOperation:[ NSBlockOperation blockOperationWithBlock:^{
        ... do method two ...
    } ] ];
}

编辑3:

根据我们的讨论:

我指出isExecuting是一个成员变量,只引用被查询的操作的状态,而不是该类的任何实例正在执行

因此,Deimus的解决方案无法使多个操作实例同时运行,例如

答案 1 :(得分:0)

抱歉,我迟到了。如果你的方法是基于定时器回调的,并且你希望它们相互之间同时执行,但与自身同步,我可以建议使用GCD定时器。

基本上,你有两个定时器,一个执行methodOne,另一个执行methodTwo。由于您将块传递给GCD计时器,因此您甚至不必使用方法,特别是如果您想确保其他代码在不应该运行时不调用这些方法。

如果将计时器安排到并发队列上,则两个计时器可能同时在不同的线程上运行。但是,计时器本身只会在计划时运行。这是一个我刚刚破解的例子......你可以轻松地使用单身......

首先,一个辅助函数来创建一个计时器,该计时器接受一个在计时器触发时将被调用的块。块传递对象,因此块可以引用它而不创建保留周期。如果我们使用self作为参数名称,则块中的代码可以像其他代码一样......

static dispatch_source_t setupTimer(Foo *fooIn, NSTimeInterval timeout, void (^block)(Foo * self)) {
    // Create a timer that uses the default concurrent queue.
    // Thus, we can create multiple timers that can run concurrently.
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    uint64_t timeoutNanoSeconds = timeout * NSEC_PER_SEC;
    dispatch_source_set_timer(timer,
                              dispatch_time(DISPATCH_TIME_NOW, timeoutNanoSeconds),
                              timeoutNanoSeconds,
                              0);
    // Prevent reference cycle
    __weak Foo *weakFoo = fooIn;
    dispatch_source_set_event_handler(timer, ^{
        // It is possible that the timer is running in another thread while Foo is being
        // destroyed, so make sure it is still there.
        Foo *strongFoo = weakFoo;
        if (strongFoo) block(strongFoo);
    });
    return timer;
}

现在,基本的类实现。如果您不想公开methodOne和methodTwo,则没有理由创建它们,特别是如果它们很简单,因为您可以直接将该代码放在块中。

@implementation Foo {
    dispatch_source_t timer1_;
    dispatch_source_t timer2_;
}

- (void)methodOne {
    NSLog(@"methodOne");
}

- (void)methodTwo {
    NSLog(@"methodTwo");
}

- (id)initWithTimeout1:(NSTimeInterval)timeout1 timeout2:(NSTimeInterval)timeout2 {
    if (self = [super init]) {
        timer1_ = setupTimer(self, timeout1, ^(Foo *self) {
            // Do "methodOne" work in this block... or call it.
            [self methodOne];
        });
        timer2_ = setupTimer(self, timeout2, ^(Foo *self) {
            // Do "methodOne" work in this block... or call it.
            [self methodTwo];
        });
        dispatch_resume(timer1_);
        dispatch_resume(timer2_);
    }
    return self;
}

- (void)dealloc {
    dispatch_source_cancel(timer2_);
    dispatch_release(timer2_);
    dispatch_source_cancel(timer1_);
    dispatch_release(timer1_);
}
@end

修改 回应这些评论(更多细节,希望能够解释为什么不会同时执行该块,以及为什么错过的计时器合并为一个)。

您无需检查它是否多次运行。直接来自文档......

  

派遣来源不可重入。任何收到的活动   dispatch源被挂起或者事件处理程序块是   当前正在执行的是在发送后合并并交付   源已恢复或事件处理程序块已返回。

这意味着当调度GCD dispatch_source定时器块时,在已经运行的定时器块完成之前,不会再次调度它。你什么也不做,库本身将确保块不会同时多次执行。

如果该块花费的时间超过了定时器间隔,那么“下一个”定时器调用将等待,直到正在运行的那个完成。此外,所有已经交付的事件将合并为一个单一事件。

你可以打电话

unsigned numEventsFired = dispatch_source_get_data(timer);

从你的处理程序中获取自上次执行处理程序以来已经触发的事件数量(例如,如果你的处理程序通过4个计时器启动,这将是4 - 但你仍然会得到所有这些启动这一个事件 - 你不会为他们收到单独的事件。)

例如,假设您的间隔计时器为1秒,而您的计时器恰好需要5秒才能运行。在当前块完成之前,该计时器不会再次触发。此外,所有这些计时器将合并为一个,所以你将有一个电话进入你的电话,而不是5。

现在,说了这么多,我应该提醒你我认为可能是一个错误。现在,我很少在库代码的脚下放置错误,但这个错误是可重复的,并且似乎违背了文档。所以,如果它不是一个bug,它就是一个没有文档记录的功能。但是,它很容易到处走。

当使用计时器时,我注意到合并的计时器肯定会被合并。这意味着,如果您的计时器处理程序正在运行,并且在计时器运行时触发了5个计时器,则将立即调用该块,表示那些错过的5个事件。但是,只要完成一次,无论之前错过了多少计时器事件,该块都将再次执行一次。

很容易识别这些,因为dispatch_source_get_data(timer)将返回0,这意味着自上次调用块以来没有触发定时器事件。

因此,我已经习惯于将此代码添加为我的计时器处理程序的第一行......

if (dispatch_source_get_data(timer) == 0) return;