3

我需要你们对我的应用程序设计的建议,基本上我想知道它是否会按我的预期工作?由于多线程是非常棘手的事情,我想听听你的意见。

基本上我的任务很简单- 我已经SomeBigSingletonClass- 大单例类,它有两个方法someMethodOnesomeMethodTwo 这些方法应该定期调用(基于计时器)并在单独的线程中调用。但是此时每个线程应该只有一个实例,例如someMethodOne,任何时候都应该只有一个运行,对于someMethodTwo.

我试过的

GCD - 使用 GCD 实现但它缺乏非常重要的功能,它不提供检查当前是否有任何正在运行的任务的方法,即我无法检查是否只有一个正在运行的 let saysomeMethodOne方法实例。

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操作类也是如此。

4

2 回答 2

2

如果您正在使用NSOperation,您可以实现您想要创建自己的内容NSOperationQueue并将其设置numberOfConcurrentOperations为 1。

您也可以将@synchronized您的类的范围用作锁定对象。

编辑:澄清---

我的提议:

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

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

编辑 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 的解决方案无法保持多个操作实例同时运行

于 2012-07-21T18:45:50.247 回答
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

编辑 回应评论(希望能更详细地解释为什么该块不会同时执行,以及为什么错过的计时器合并为一个)。

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

调度源不可重入。在调度源挂起或事件处理程序块当前正在执行时接收到的任何事件都会在调度源恢复或事件处理程序块返回后合并和传递。

这意味着当 GCD dispatch_source 计时器块被调度时,它不会再次被调度,直到已经运行的计时器块完成。您什么都不做,库本身将确保该块不会同时执行多次。

如果该块花费的时间比计时器间隔长,那么“下一个”计时器调用将等到正在运行的那个完成。此外,所有本应交付的事件都合并为一个事件。

你可以打电话

unsigned numEventsFired = dispatch_source_get_data(timer);

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

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

现在,说了这么多,我应该提醒你我认为可能是一个错误。现在,我很少在库代码的脚下放置错误,但是这个是可重复的,并且似乎与文档背道而驰。所以,如果它不是一个错误,它就是一个未记录的特性。但是,很容易四处走动。

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

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

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

if (dispatch_source_get_data(timer) == 0) return;
于 2012-07-21T20:39:35.107 回答