0

我一直在考慮一個看起來很容易實現的問題,但一個高效且線程安全的解決方案阻礙了我的發展。我想要做的是創建某種工作對象。幾個呼叫者可能會要求它從不同的線程工作。一個要求是請求不能排隊。換句話說,如果有人要求工作人員做工,但看到工作已經開始,那麼應該早點回來。關於使這個線程安全,但有效的建議?

一個簡單的第一階段是這樣的:

@interface Worker : NSObject 
@property (nonatomic, assign, getter = isWorking) BOOL working; 
- (void)doWork; 
@end 

@implementation Worker 
{ 
    dispatch_queue_t _workerQueue; //... a private serial queue 
} 

- (void)doWork 
{ 
    if (self.isWorking) 
    { 
     return; 
    } 
    self.working = YES; 
    dispatch_async(_workerQueue, ^{ 
     // Do time consuming work here ... Done! 
     self.working = NO; 
    }); 
} 
@end 

這樣做的問題是,isWorking屬性不是線程安全的。將它標記爲原子將不會有幫助,因爲對它的訪問需要在幾條語句中同步。

爲了讓線程安全的,我需要保護isWorking用鎖:

@interface Worker : NSObject 
@property (nonatomic, assign, getter = isWorking) BOOL working; 
- (void)doWork; 
@end 

@implementation Worker 
{ 
    dispatch_queue_t _workerQueue; //... a private serial queue 
    NSLock *_lock; // assume this is created 
} 

- (void)doWork 
{ 
    [_lock lock]; 
    if (self.isWorking) 
    { 
     [_lock unlock]; 
     return; 
    } 
    self.working = YES; 
    [_lock unlock]; 
    dispatch_async(_workerQueue, ^{ 
     // Do time consuming work here ... Done! 
     [_lock lock]; 
     self.working = NO; 
     [_lock unlock]; 
    }); 
} 

@end

雖然我相信這將是線程安全的,我認爲這是非常糟糕到必須採取並經常放棄鎖定(昂貴的操作)。

那麼,有沒有更優雅的解決方案?

回答

3

dispatch_semaphore is the idiomatic way to limit access to a finite resource,如果您已經在使用GCD。

// Add an ivar: 
dispatch_semaphore_t _semaphore; 

// To initialize: 
_semaphore = dispatch_semaphore_create(1); 

// To "do work" from any thread: 
- (void)doWork 
{ 
    if (dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_NOW) == 0) { 
     // We got the semaphore without waiting, so we're first in line. 
     dispatch_async(_workerQueue, ^{ 
      // do time consuming work here, then when done: 
      dispatch_semaphore_signal(_semaphore); 
     }); 
    } else { 
     // We would have had to wait for the semaphore, so somebody must have 
     // been doing work already, and we should do nothing. 
    } 
} 

Here's a blog post explaining in more detail.

+0

不錯。我開始自己走上這條路。然而,讓外部人員工作的財產問問並看看工作人員是否工作仍然會很好。如果我想,看起來我仍然需要找到一種讀取或寫入原子的方式。 –

+1

似乎那財產不可靠,不是嗎?當你給一個調用者返回一個值時,它可能會過時。無論如何,你總是可以:'if(dispatch_semaphore_wait(_semaphore,DISPATCH_TIME_NOW)== 0){isWorking = NO; dispatch_semaphore_signal(_semaphore); } else {isWorking = YES; }'。 –

+0

你說得很好,我同意。我猜想我只是在想假設,如果一個外部調用者想問工人和工作人員是否正在改變isWorking的價值,那麼我只是感興趣,如果單獨是線程安全的還是競爭條件的話。 –

2

您可能可以在此處使用原子test-and-set操作。 GCC爲此提供__atomic_test_and_set。這裏是你如何使用它在C(未經測試):

static volatile bool working = FALSE; 
if(__atomic_test_and_set(&working, __ATOMIC_ACQUIRE)) { 
    // Already was working. 
}else{ 
    // Do work, possibly in another thread. 
    // When done: 
    __atomic_clear(&working, __ATOMIC_RELEASE); 
} 

很簡單,是吧?

+0

嗯這看起來很酷。然而,每個人都知道我們現在生活在Cla world世界:)。這僅僅是GCC嗎? –

+1

[它幾個月前被添加到clang](http://llvm.org/viewvc/llvm-project?view=rev&revision=154640)。可能還可能不在Xcode中。 [OSAtomic.h](http://developer.apple.com/library/mac/#documentation/System/Reference/OSAtomic_header_reference/Reference/reference.html)中的等價函數同樣是輕量級的。 –

0

爲了使屬性線程安全,您可以簡單地使用@synchronize。

+0

我不知道如何單獨使用@synchronize來解決我的問題。你應該寫一些代碼來說明你的建議。 –

+0

如果你有一個簡單的原子getter/setter通過屬性,那麼你可以通過這個更新任何線程的工作,它將是完全線程安全的。 – Cocoanetics