2013-06-18 70 views
2

我試圖以線程安全的方式控制網絡活動指示器。Objective-C線程安全計數器

以下是我現在正在做的方式,但我認爲必須有更好的方法來做到這一點。我正在尋找使用鎖,但它看起來像一個昂貴的操作。我一直在看OSAtomicAdd,但不知道如何在這種情況下使用它。

+ (void)start 
{ 
    [self counterChange:1]; 
} 

+ (void)stop 
{ 
    [self counterChange:-1]; 
} 

+ (void)counterChange:(NSUInteger)change 
{ 
    static NSUInteger counter = 0; 
    static dispatch_queue_t queue; 
    if (!queue) { 
     queue = dispatch_queue_create("NetworkActivityIndicator Queue", NULL); 
    } 
    dispatch_sync(queue, ^{ 
     if (counter + change <= 0) { 
      counter = 0; 
      [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; 
     } else { 
      counter += change; 
      [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; 
     } 
    }); 
} 

怎麼能這樣做使用OSAtomicAdd?

+1

也許你可以使用http://stackoverflow.com/questions/16420340/fixing-my-network-activity-indicator/16420875#16420875一些代碼。 –

+0

@MartinR謝謝,你的回答實際上解決了我的問題。我想確保NumberOfCallsToSetVisible永遠不會變成-1。 NumberOfCallsToSetVisible = 0線程安全還是有一個osatomic集? –

+0

我也喜歡MartinR的答案,但是如果您想要序列化,請在類級setter +(void)setCounter上使用@synchronize(或等價物):並確保在增量時使用setter。 – danh

回答

3

單靠OSAtomicAdd這樣的東西就不能僅僅依靠這樣的東西來同步這種操作。整個操作需要鎖定,以確保其成功運行。

考慮this answer建議的解決方案,它基本上可以歸結爲這樣:

static volatile int32_t NumberOfCallsToSetVisible = 0; 
int32_t newValue = OSAtomicAdd32((setVisible ? +1 : -1), &NumberOfCallsToSetVisible); 
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:(newValue > 0)]; 

如果這個代碼是從一個線程調用,setVisible設置爲YES,調用OSAtomicAdd32是要加1 NumberOfCallsToSetVisible導致newValue被設置爲1

現在考慮如果在執行下一行之前線程被搶佔發生了什麼,而另一個線程用setVisible設置爲調用函數。這一次調用OSAtomicAdd32將要從NumberOfCallsToSetVisible導致newValue 1。減去被設置爲0。

如果此第二線程繼續,並且執行下一行,newValue是不大於零,所以setNetworkActivityIndicatorVisible方法將請撥打NO。在這一點上,活動指標無論如何都是不可見的,所以這沒有任何作用,但它也沒有任何傷害。

然而,最終我們要切換回哪裏newValue設置爲1的第一個線程所以當線程執行的下一行,newValue明顯大於零,且setNetworkActivityIndicatorVisible方法將YES被稱爲,使活動指示符可見。

因此,我們已經調用的函數一旦與setVisible設置爲YES,最後再用setVisible設置爲NO。你會期望這會導致活動指標不可見,但這並非如此。事實上,如果沒有其他呼叫,它將永遠保持可見。這顯然是不正確的。

底線是你需要將整個東西包裝在@synchronize塊或類似的東西。

+0

你說得對,我的回答並不能可靠地解決問題。 –

+0

您錯過了值不低於零的要求,是嗎? (我不確定你需要把你的計數器標記爲'volatile')。 – yonosoytu

+0

@yonosoytu:該函數被聲明爲int32_t OSAtomicAdd32(int32_t __theAmount,volatile int32_t * __ theValue);' –

1

而不是OSAtomicAdd32,我會推薦使用來自同一功能家族的OSAtomicCompareAndSwap32

+ (void)counterChange:(NSUInteger)change 
{ 
    static int32_t counter = 0; 
    int32_t localCounter, newCounter; 
    do 
    { 
    localCounter = counter; 
    newCounter = localCounter + change; 
    newCounter = newCounter <= 0 ? 0 : newCounter; 
    } while (!OSAtomicCompareAndSwap32(localCounter, newCounter, &counter)); 
    [UIApplication sharedApplication].networkActivityIndicatorVisible = counter > 0; 
} 

功能將比較localCounter反對counter當前值,且僅當它們匹配它將改變counternewCounter,它的所有原子。如果其他線程更改counter在當前線程需要localCounter和撥打OSAtomicCompareAndSwap32之間,檢查將失敗,並且它將重試。

即使看起來它可能會留下一些線程永遠循環,這種結構在現實世界中足夠安全。

+0

想象一下,當'localCounter'被設置爲0後,調用'counterChange:1'被另一個調用'counterChange:1'的線程搶佔。在第二個線程中'counter'變爲1,所以回到第一個線程'newCounter'最終會被設置爲2.如果第一個線程在'OSAtomiccompareAndSwap32'調用之前被另一個調用'counterChange:-1'的線程再次搶佔,'counter'將返回到0以允許調用成功,但是結果會是'counter'在它應該是1時更新爲2.這只是我可以想象這種代碼失敗的幾種方法之一。 –

+0

是的,我修正了代碼,該行應該讀取'localCounter'。關於幾種方法之一,這可能會失敗......就我所知,'OSAtomicAdd32'是用你在代碼中看到的東西來實現的,除非在處理器中有更好的基元。 – yonosoytu

+0

然後,這是另一個缺陷。假設你有一個調用'counterChange:1'的函數,它最後一行計算'counter> 0',但是在將'networkActivityIndi​​catorVisible'設置爲'YES'之前被第二個線程搶佔。第二個線程調用'counterChange:-1'並結束執行減少了'counter'的函數返回到零,所以'networkActivityIndi​​catorVisible'設置爲'NO'。當我們切換回第一個線程時,它將繼續停止將「networkActivityIndi​​catorVisible」設置爲「YES」,但這顯然不是它應該的。 –

0

NSLock(適用於iOS 2.0+和OS X 10.0+)是您正在尋找的。

NSLock對象用於協調同一應用程序中多個執行線程的操作。 NSLock對象可用於調解對應用程序全局數據的訪問或保護代碼的關鍵部分,從而允許它以原子方式運行。

你可以在你的應用程序代理初始化鎖,並呼籲它-lock-unlock周邊計數器代碼:

// Assuming the application delegate implements -counterChangeLock 
// to return a momoized instance of NSLock 

+ (NSLock *)counterChangeLock 
{ 
    return [(AppDelegate *)([UIApplication sharedApplication].delegate) counterChangeLock]; 
} 

+ (void)start 
{ 
    [[self counterChangeLock] lock]; // blocks if counter is locked already 

    // safely increment counter 

    [[self counterChangeLock] unlock]; 
} 

+ (void)stop 
{ 

    [[self counterChangeLock] lock]; 

    // safely decrement counter 

    [[self counterChangeLock] unlock]; 
} 
0

我不知道爲什麼正在使用這些原子操作時,它複雜化這個問題並沒有解決我們需要修復線程同步的事實,因爲它告訴UIApplication我們需要的數量。

使用@synchronized的建議是正確的解決方案,因爲它爲您提供了一個增量和調用UIApplication的互斥體。如果基準@synchronized,你會看到它的速度驚人的快,這種事情是罕見的,原子變量&比較和交換是容易出錯和不必要的。不這樣做的唯一原因是如果(自我)在許多其他部分同步,在這種情況下,您可以爲此保留NSObject或使用NSLock &等價物。

因此:

+ (void) incrementActivityCounter { 
    [self changeActivityCounter:1]; 
} 

+ (void) decrementActivityCounter { 
    [self changeActivityCounter:-1]; 
} 

+ (void) changeActivityCounter:(int)change { 
    static int counter = 0; 
    @synchronized(self) { 
     counter += change; 
     [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:counter > 0]; 
    } 
} 
0

的UIApplication的networkActivityIndi​​catorVisible是一個非原子屬性,因此它應該只從主線程使用。因此,不需要同步計數器,因爲它不應該從線程調用。一個簡單的靜態int和增量的停止開始和減少是所有需要的。