美文网首页Swift&Objective-C
NSMutableArray并发场景下的内存越界问题

NSMutableArray并发场景下的内存越界问题

作者: 凌峰 | 来源:发表于2018-04-02 15:24 被阅读7次

    最近使用ObjC和Swift混合编程,发现ios的ARC策略在并发下还是存在很大的问题。我的看法是,如果涉及到并发编程,最好还是不要过于依赖ARC机制。

    以下是问题:

    我们在一个需要处理实时数据的App里面,考虑采用NSMutableArray实现一个简易的FIFO队列。逻辑是这样的:

    @implementation UTKSimpleQueue
    @synthesize capacity = _capacity;
    
    - (instancetype) initWithCapacity:(int)capacity {
        if (self = [super init]) {
            queue = [[NSMutableArray alloc] init];
            _capacity = capacity;
            availability = 0;
        }
        return self;
    }
    
    - (BOOL) push:(NSObject*)obj {
        BOOL result = YES;
        [lck lock];
        if (availability >= _capacity) {
            result = NO;
        }
        else {
            [queue addObject: obj];
            availability ++;
        }
        [lck unlock];
        return result;
    }
    
    - (NSObject*) pop {
        NSObject *obj = nil;
        [lck lock];
        if (availability > 0) {
            availability --;
            obj = queue[availability];
            [queue removeObjectAtIndex:availability];
        }
        [lck unlock];
        
        return obj;
    }
    
    - (void) flush {
        [lck lock];
        [queue removeAllObjects];
        availability = 0;
        [lck unlock];
    }
    
    - (NSObject*) peek {
        NSObject *obj = nil;
        [lck lock];
        if (availability > 0) {
           obj = queue[availability];
        }
        [lck unlock];
        return obj;
    }
    
    @end
    

    严格来说,这是一个blocking队列,采用了NSLock实现数据的线程安全。在其他的语言下,一个FIFO blocking队列是最容易实现的结构。但是我们没有想到的是,NSMutableArray在push与pop操作时,会频繁遇到NSZombie和线程的不同步问题。例如在两个线程里,采用这样的代码

    UTKSimpleQueue *inputQueue;
    UTKSimpleQueue *outputQueue;
    
    //thread 1:
    NSObject *obj;
    [inputQueue push:obj];
    ...
    //thread 2:
    NSObject obj = [inputQueue pop];
    [outputQueue push:obj];
    

    就会发生message send to dealloc object的报错,核查之后发现inputQueue当中存储了NSZombie_...的对象。

    这个问题是偶发的,随机的。在痛苦的尝试了一周的调试之后,最终,我决定放弃利用NSMutableArray,采用最直接的双向链表实现了一个线程安全的BlockingQueue:

    @implementation UTKBlockingQueue
    
    - (instancetype) initWithCapacity:(int)capacity {
        if (self = [super init]) {
            _capacity = capacity;
            _availability = 0;
            lock = [[NSLock alloc] init];
            first = nil;
            last = first;
        }
        return self;
    }
    
    - (BOOL) push:(NSObject*)obj {
        BOOL result = true;
    
        [lock lock];
        if (_availability >= _capacity) {
            result = NO;
        }
        else {
            UTKBlockingQueueItem *item = [[UTKBlockingQueueItem alloc] init];
            _availability ++;
            if (!first) {
                item.prev = nil;
                item.next = nil;
                item.obj = obj;
                first = item;
            }
            else {
                item.prev = last;
                item.next = nil;
                item.obj = obj;
            }
            last.next = item;
            last = item;
            result = YES;
        }
        [lock unlock];
        
        return result;
    }
    
    - (NSObject*) pop {
        if (!first) {
            return nil;
        }
        else {
            UTKBlockingQueueItem *item = first;
            
            [lock lock];
            first = first.next;
            first.prev = nil;
            _availability --;
            [lock unlock];
            
            NSObject *obj = item.obj;
            item.next = nil;
            item.obj = nil;
            return obj;
        }
    }
    
    - (NSObject*) peak {
        return first.obj;
    }
    
    - (void) flush {
        [lock lock];
        while(first && first.next) {
            UTKBlockingQueueItem *item = first;
            first = item.next;
            first.prev = nil;
            
            item.next = nil;
            item.prev = nil;
            item.obj = nil;
            
            item = first;
        }
        first.prev = nil;
        first.obj = nil;
        last = first;
        last = nil;
        _availability = 0;
        [lock unlock];
    }
    
    - (int) remain {
        return _availability;
    }
    @end
    
    @implementation UTKBlockingQueueItem
    @synthesize obj;
    @synthesize prev;
    @synthesize next;
    
    - (instancetype) init {
        if (self = [super init]){
            self.obj = nil;
            self.prev = nil;
            self.next = nil;
        }
        return self;
    }
    @end
    

    因此,在ObjC涉及到多线程并发操作和需要动态申请内存的应用场景下,我的个人建议是:

    1. malloc操作之务必进行bzero操作
    2. 高频队列操作不要选择NSArray和NSMutableArray
    3. 所有的指针释放之后手动设置为NULL

    相关文章

      网友评论

        本文标题:NSMutableArray并发场景下的内存越界问题

        本文链接:https://www.haomeiwen.com/subject/uwcxhftx.html