2011-05-01 56 views
5

我有一個包含1000個條目的第二個字典的字典。這些條目都是類型鍵的NSString = key XXX,值= element XXX其中XXX是一個介於0 - 元素數-1之間的數字。(幾天前,我問及包含字典的Objective-C詞典請求refer to that question if您需要創建字典的代碼。)Objective-c:與塊和NS EnumerationConcurrent有關的問題

子字典中所有字符串的總長度爲28,670個字符。即:

strlen("key 0")+strlen("element 0")+ 
//and so on up through 
strlen("key 999")+strlen("element 999") == 28670. 

考慮這是一個非常簡單的散列值作爲指標,如果一個方法每次只鍵入一次鍵值對。

我有一個子程序(使用塊)訪問個體詞典鍵和值的完美的作品:如果我嘗試同樣使用併發塊(多處理器機器上)

NSUInteger KVC_access3(NSMutableDictionary *dict){ 
    __block NSUInteger ll=0; 
    NSMutableDictionary *subDict=[dict objectForKey:@"dict_key"]; 

    [subDict 
     enumerateKeysAndObjectsUsingBlock: 
      ^(id key, id object, BOOL *stop) { 
       ll+=[object length]; 
       ll+=[key length]; 
    }]; 
    return ll; 
} 
// will correctly return the expected length... 

,我得到一個數接近但不完全是預期的28670:

NSUInteger KVC_access4(NSMutableDictionary *dict){ 
    __block NSUInteger ll=0; 
    NSMutableDictionary *subDict=[dict objectForKey:@"dict_key"]; 

    [subDict 
     enumerateKeysAndObjectsWithOptions: 
      NSEnumerationConcurrent 
     usingBlock: 
      ^(id key, id object, BOOL *stop) { 
       ll+=[object length]; 
       ll+=[key length]; 
    }]; 
    return ll; 
} 
// will return correct value sometimes; a shortfall value most of the time... 

蘋果文檔的NSEnumerationConcurrent狀態:

"the code of the Block must be safe against concurrent invocation." 

我認爲這可能是問題,但是我的代碼或KVC_access4中的塊對於併發調用是不安全的問題是什麼?

編輯&結論

感謝BJ荷馬的excellent solution,我NSEnumerationConcurrent工作。我廣泛地定時了這兩種方法。我在KVC_access3以上的代碼對於中小型字典更快更容易。它在很多詞典上快得多。但是,如果你有一個蒙戈大辭典(百萬或上千萬鍵/值對的),那麼這段代碼:

[subDict 
    enumerateKeysAndObjectsWithOptions: 
     NSEnumerationConcurrent 
    usingBlock: 
     ^(id key, id object, BOOL *stop) { 
     NSUInteger workingLength = [object length]; 
     workingLength += [key length]; 

     OSAtomicAdd64Barrier(workingLength, &ll); 
}]; 

是快4倍。大小的交叉點大約是我的測試元素的100,000個字典的1個字典。大概由於設置時間,更多的字典和交叉點更高。

回答

13

使用併發枚舉,您可以讓該塊在多個線程上同時運行。這意味着多個線程同時訪問ll。由於你沒有同步,你很容易出現競爭條件。

這是一個問題,因爲+=操作不是原子操作。請記住,ll += xll = ll + x是一樣的。這包括讀取ll,將x添加到該值,然後將新值存回ll。在線程X上讀取ll的時間以及存儲時間之後,當線程X返回存儲其計算時,由其他線程引起的任何更改都將丟失。

您需要添加同步,以便多個線程不能同時修改該值。天真的解決辦法是這樣的:

__block NSUInteger ll=0; 
NSMutableDictionary *subDict=[dict objectForKey:@"dict_key"]; 

[subDict 
    enumerateKeysAndObjectsWithOptions:NSEnumerationConcurrent 
    usingBlock: 
     ^(id key, id object, BOOL *stop) { 
      @synchronized(subDict) { // <-- Only one thread can be in this block at a time. 
       ll+=[object length]; 
       ll+=[key length]; 
      } 
}]; 
return ll; 

然而,這種放棄你從併發枚舉得到,因爲該塊的整個身體現在被封閉在一個synchronized塊,實際上,只有該塊一個實例的所有好處實際上會一次運行。

如果併發實際上是一個顯著的性能要求在這裏,我建議如下:

__block uint64 ll = 0; // Note the change in type here; it needs to be a 64-bit type. 

^(id key, id object, BOOL *stop) { 
    NSUInteger workingLength = [object length]; 
    workingLength += [key length]; 

    OSAtomicAdd64Barrier(workingLength, &ll); 
} 

請注意,我用OSAtomicAdd64Barrier,這是保證增加一個相當低的水平功能價值原子。您也可以使用@synchronized來控制訪問權限,但如果此操作實際上是一個重要的性能瓶頸,那麼您可能需要最高性能的選項,即使代價是稍微清晰一些。如果這感覺像是過度殺毒,那麼我懷疑啓用併發枚舉並不會真的影響你的性能。