3

ここで私のアプリのデザインについてアドバイスが必要です。基本的に、期待どおりに機能するかどうかを知りたいですか?マルチスレッドはかなりトリッキーなことなので、私はあなたから聞きたいです。

基本的に、私のタスクは非常に単純です-私はSomeBigSingletonClass-大きなシングルトンクラスで、2つのメソッドがsomeMethodOneあり、someMethodTwo これらのメソッドは定期的に(タイマーベースで)別々のスレッドで呼び出す必要があります。ただし、現時点では各スレッドのインスタンスは1つだけである必要があります。たとえばsomeMethodOne、いつでも1つだけ実行され、は同じである必要がありsomeMethodTwoます。

私が試したこと

GCD -GCDで実装しましたが、非常に重要な機能がありません。現時点で実行中のタスクがあるかどうかを確認する手段がありません。つまり、letsaysomeMethodOneメソッドの実行中のインスタンスが1つだけかどうかを確認できませんでした。

NSThread-優れた機能を提供しますが、NSOperationやGCDなどの新しい高レベルのテクノロジーにより、コードの保守がより簡単になると確信しています。そこで、NSThreadをあきらめることにしました。

NSOperation 2つのスレッドの呼び出しを実装する方法に関する私のソリューション

@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

を使用している場合は、独自に作成して1に設定しNSOperationたいものを実現できます。NSOperationQueuenumberOfConcurrentOperations

@synchronizedクラスでスコープをロックオブジェクトとして使用することもできます。

編集:説明---

私が提案していること:

キューA(1つの同時操作-SomeMethodOneOperation SomeMethodTwoOperation一度に1回実行するために使用)

キューB(n個の同時操作-実行する一般的なバックグラウンド操作に使用)

編集2:最大操作1と操作2を実行するためのアプローチを示すコードを更新し、操作1と操作2のそれぞれ最大1つを任意の時点で実行します。

-(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タイマーを使用することをお勧めします。

基本的に、2つのタイマーがあります。1つはmethodOneを実行し、もう1つは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

コメントに応じて編集 します(ブロックが同時に実行されない理由、および欠落したタイマーが1つに統合される理由を説明するための詳細を含む)。

複数回実行されているかどうかを確認する必要はありません。ドキュメントから直接...

ディスパッチソースは再入可能ではありません。ディスパッチソースが一時停止されている間、またはイベントハンドラブロックが現在実行されている間に受信されたイベントは、ディスパッチソースが再開された後、またはイベントハンドラブロックが返された後に合体して配信されます。

つまり、GCD dispatch_sourceタイマーブロックがディスパッチされると、すでに実行されているものが完了するまで、それは再度ディスパッチされません。何もしません。ライブラリ自体が、ブロックが同時に複数回実行されないようにします。

そのブロックにタイマー間隔よりも長い時間がかかる場合、「次の」タイマー呼び出しは、実行中のタイマー呼び出しが完了するまで待機します。また、配信されたはずのすべてのイベントが1つのイベントにまとめられます。

あなたは呼び出すことができます

unsigned numEventsFired = dispatch_source_get_data(timer);

ハンドラー内から、ハンドラーが最後に実行されてから発生したイベントの数を取得します(たとえば、ハンドラーが4回のタイマー起動を実行した場合、これは4回になりますが、この1回のイベントですべての発生が発生します-それらの個別のイベントを受け取ることはありません)。

たとえば、インターバルタイマーが1秒で、タイマーの実行に5秒かかるとします。そのタイマーは、現在のブロックが完了するまで再び起動しません。さらに、これらのタイマーはすべて1つに統合されるため、5つではなく、1つのブロックに1つの呼び出しがあります。

さて、そうは言っても、バグだと思うことについて注意する必要があります。今、私はライブラリコードの足元にバグを置くことはめったにありませんが、これは繰り返し可能であり、ドキュメントに反しているようです。したがって、バグでない場合は、文書化されていない機能です。ただし、回避するのは簡単です。

タイマーを使用するとき、合体したタイマーが最も確実に合体することに気づきました。つまり、タイマーハンドラーが実行されていて、実行中に5つのタイマーが起動された場合、ブロックはすぐに呼び出され、見逃された5つのイベントを表します。ただし、それが完了するとすぐに、以前に見逃されたタイマーイベントの数に関係なく、ブロックは1回だけ再度実行されます。

ただし、dispatch_source_get_data(timer)は0を返すため、これらを簡単に識別できます。これは、ブロックが最後に呼び出されてからタイマーイベントが発生していないことを意味します。

したがって、私はこのコードをタイマーハンドラーの最初の行として追加することに慣れてきました...

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