建立一個原子NSMutableArray類
阿新 • • 發佈:2019-02-05
在Cocoa Framework以及GNUStep的Foundation庫中,NSMutableArray以及NSMutableDictionary的設計不是多執行緒安全的,當然這種設計的好處是處理速度快,不需要任何鎖進行同步,所以我們在使用Objective-C的這些容器的時候需要注意,在哪個執行緒中建立它們就在哪個執行緒中對它們進行操作。不過在某些情況下,我們由於一些演算法或業務需求,需要在多個執行緒中共享一個NSMutableArray容器物件,這時候我們需要通過一些同步機制來實現多執行緒操作的安全性。
在諸多同步方法中,使用原子操作無疑是輕量且能保證多核多執行緒安全的,因此這裡就通過原子操作來實現一個多核多執行緒安全的NSMutableArray類。這裡採用的方法是將NSMutableArray物件以聚合的方式作為自定義陣列類的一個成員屬性。這麼做的好處是靈活、可裁剪,同時對於全域性名字空間也不會造成太過混亂的問題,儘管我們也可以用Category對已有的NSMutableArray類進行擴充,但過度濫用Category反而會讓整個專案的名字空間造成混亂。
#import <Foundation/Foundation.h> #include <stdalign.h> #include <stdatomic.h> #if defined(__i386__) || defined(__x86_64__) #define CPU_PAUSE() asm("pause") #elif defined(__arm__) || defined(__arm64__) #define CPU_PAUSE() asm("yield") #else #define CPU_PAUSE() #endif @interface ZCAtomicMutableArray<__covariant T> : NSObject - (instancetype)initWithArray:(NSArray<T>*)array; - (void)addObject:(T)object; - (void)removeObject:(T)object; - (void)removeObjectAtIndex:(NSUInteger)index; - (void)setObject:(T)anObject atIndexedSubscript:(NSUInteger)index; - (T)objectAtIndexedSubscript:(NSUInteger)index; - (NSUInteger)count; - (NSMutableArray*)getUnsafeArray; @end @implementation ZCAtomicMutableArray { @private NSMutableArray *mArray; atomic_int alignas(16) mFlag; } - (instancetype)initWithArray:(NSArray*)array { self = [super init]; if(array == nil) mArray = [[NSMutableArray alloc] initWithCapacity:1024]; else mArray = [[NSMutableArray alloc] initWithArray:array]; atomic_init(&mFlag, 1); return self; } - (void)dealloc { while(atomic_exchange(&mFlag, 0) == 0) CPU_PAUSE(); [mArray removeAllObjects]; [mArray release]; atomic_store(&mFlag, 1); NSLog(@"ZCAtomicMutableArray deallocated!"); [super dealloc]; } - (void)addObject:(id)object { while(atomic_exchange(&mFlag, 0) == 0) CPU_PAUSE(); [mArray addObject:object]; atomic_store(&mFlag, 1); } - (void)removeObject:(id)object { while(atomic_exchange(&mFlag, 0) == 0) CPU_PAUSE(); [mArray removeObject:object]; atomic_store(&mFlag, 1); } - (void)removeObjectAtIndex:(NSUInteger)index { while(atomic_exchange(&mFlag, 0) == 0) CPU_PAUSE(); [mArray removeObjectAtIndex:index]; atomic_store(&mFlag, 1); } - (void)setObject:(id)anObject atIndexedSubscript:(NSUInteger)index { while(atomic_exchange(&mFlag, 0) == 0) CPU_PAUSE(); [mArray setObject:anObject atIndexedSubscript:index]; atomic_store(&mFlag, 1); } - (id)objectAtIndexedSubscript:(NSUInteger)index { while(atomic_exchange(&mFlag, 0) == 0) CPU_PAUSE(); id ret = [mArray objectAtIndexedSubscript:index]; atomic_store(&mFlag, 1); return ret; } - (NSUInteger)count { while(atomic_exchange(&mFlag, 0) == 0) CPU_PAUSE(); NSUInteger length = mArray.count; atomic_store(&mFlag, 1); return length; } - (NSMutableArray*)getUnsafeArray { return mArray; } @end int main(int argc, const char * argv[]) { @autoreleasepool { ZCAtomicMutableArray<NSNumber*> *array = [[ZCAtomicMutableArray alloc] initWithArray:@[@10, @20, @30, @40, @50]]; NSLog(@"The count is: %tu", [array count]); NSProcessInfo *proc = [NSProcessInfo processInfo]; NSTimeInterval beginTime = proc.systemUptime; for(int i = 0; i < 1000000; i++) [array addObject:@(i)]; NSLog(@"Time spent: %f", (proc.systemUptime - beginTime) * 1000.0); [array release]; NSMutableArray *ma = [[NSMutableArray alloc] initWithCapacity:1024]; beginTime = proc.systemUptime; for(int i = 0; i < 1000000; i++) [ma addObject:@(i)]; NSLog(@"Time spent: %f", (proc.systemUptime - beginTime) * 1000.0); [ma release]; array = [[ZCAtomicMutableArray alloc] initWithArray:nil]; __block BOOL isComplete = NO; beginTime = proc.systemUptime; dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^void(void) { for(int i = 0; i < 500000; i++) [array addObject:@(i)]; isComplete = YES; }); for(int i = 500000; i < 1000000; i++) [array addObject:@(i)]; while(!isComplete) CPU_PAUSE(); NSLog(@"Time spent: %f", (proc.systemUptime - beginTime) * 1000.0); uint64 sum = 0; // Test for(int i = 0; i < 1000000; i++) sum += array[i].intValue; [array release]; NSLog(@"sum = %llu", sum); sum = 0; for(int i = 0; i < 1000000; i++) sum += i; NSLog(@"test sum = %llu", sum); } }
以上程式碼是在macOS 10.12中完成的,當然各位也可以把它放到Linux下執行,安裝Clang編譯器以及GNUStep庫、libdispatch-dev庫即可。我們在main函式的最後部分可以看到,使用雙核兩個執行緒對同一個array物件操作完全沒有問題。但是,如果這裡的array是NSMutableArray型別的話,那麼在執行時就會引發崩潰。