1. 程式人生 > >建立一個原子NSMutableArray類

建立一個原子NSMutableArray類

在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型別的話,那麼在執行時就會引發崩潰。