美文网首页
iOS 数组线程安全

iOS 数组线程安全

作者: 爬树的蚂蚁 | 来源:发表于2019-08-26 14:46 被阅读0次

    概述

    为什么会有数组的线程安全问题?
    对于可变的集合(NSMutableArray、NSMutableDictionary、NSMutableSet)是可读可写的,所以有可能出现这种场景:两个或多个线程同时对一个可变集合进行读、写、新增及删除的操作,这样是得不到预期的结果的,甚至程序会抛出异。如果我们将这些线程用一定的规则去管理好,那就可以解决这个问题了。下面我们开始着手处理这个问题吧

    先修

    要解决这个线程安全的问题,需要明白两个知识点

    1.nonatomic 和atomic

    这两个关键字是用来修饰成员变量的。前者是非原子操作即线程可以随便访问成员变量,后者是原子操作即线程访问按照一定的规则进行。

    nonatomic:

    如果只存在单个线程访问成员变量,用它修饰是非常不错的,因为没有对访问进行线程加锁,效率非常高。但是正因为没有加锁,所以可能同时进行读写,导致不可预期的错误。

    atomic:

    用atomic修饰成员变量,会给成员变量的getter 和 setter方法加锁,使访问每次只能进行一个,避免多个线程同时操作成员变量,所以适用于多线程访问成员变量的场景。
    虽然atomic修饰的成员变量在多线程去访问时不会出现错误,但结果不一定准确:

    比如说有一个成员变量name,当a线程去getter name的值,同时有b线程和c线程对name 进行setter值,那么name的值就不确定了,可能是b线程操作之前的值,也有可能是b线程操作之后的值,也有可能是c线程操作之后的值。

    再看下面这段代码:

    - (void)competition {
        self.count = 0;
    
        dispatch_async(queue1, ^{
          for (int i = 0; i < 10000; i++) {
              self.count = self.count + 1;
          }
        });
    
        dispatch_async(queue2, ^{
          for (int i = 0; i < 10000; i++) {
              self.count = self.count + 1;
          }
        });
    }
    

    上面这段代码的最终结果肯定小于20000。当获取值的时候都是原子线程安全操作,比如两个线程都获取了当前值 0,于是分别增量后变为了 1,所以两个队列依序写入值都是 1,所以不是线程安全的。

    2.dispatch_barrier_async 和dispatch_barrier_sync

    这是GDC里面的两个栅栏方法,需要配合队列使用。其作用是拦住前面添加到队列的任务,让这些任务执行完成,然后再执行栅栏里的任务,两个方法的区别是:

    • dispatch_barrier_async不阻塞主线程;
    • dispatch_barrier_sync阻塞主线程,非得等到栅栏里的任务执行完成程序才能执行主线程的任务。
    • 另外一点需要明确的是,栅栏函数只对主队列和自身所在队列有影响,其他队列不受影响。

    如果在队列中的栅栏之后再添加任务,则此任务要等到栅栏里的任务完成后才会执行。

    看一段代码就一目了然了

    先使用 dispatch_barrier_sync
    dispatch_queue_t concurrent_queue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
        dispatch_async(concurrent_queue, ^{
            for (int i = 0; i < 500; i++) {
                if (i % 100 == 0) {
                    NSLog(@"任务一%d",i);
                }
            }
        });
        
        dispatch_async(concurrent_queue, ^{
            for (int i = 0; i < 50; i++) {
                if (i % 10 == 0) {
                    NSLog(@"任务二%d",i);
                }
            }
        });
        
        dispatch_async(concurrent_queue, ^{
            for (int i = 0; i < 30; i++) {
                if (i % 5 == 0) {
                    NSLog(@"任务三%d",i);
                }
            }
        });
        
        // 这里使用同步栅栏函数
        dispatch_barrier_sync(concurrent_queue, ^{
            for (int i = 0; i < 40; i++) {
                if (i % 5 == 0) {
                    NSLog(@"-------同步barrier的任务%d-------",i);
                }
            }
        });
        
        NSLog(@"外面的任务");
        
        dispatch_async(concurrent_queue, ^{
            for (int i = 0; i < 3; i++) {
                NSLog(@"任务四%d",i);
            }
        });
        
        dispatch_async(concurrent_queue, ^{
            for (int i = 0; i < 3; i++) {
                NSLog(@"任务六%d",i);
            }
        });
    

    打印结果如下

    2019-07-31 17:22:17.599644+0800 ArrayTest[14396:408598] 任务一0
    2019-07-31 17:22:17.599644+0800 ArrayTest[14396:408597] 任务三0
    2019-07-31 17:22:17.599644+0800 ArrayTest[14396:408596] 任务二0
    2019-07-31 17:22:17.599823+0800 ArrayTest[14396:408598] 任务一100
    2019-07-31 17:22:17.599823+0800 ArrayTest[14396:408597] 任务三5
    2019-07-31 17:22:17.599824+0800 ArrayTest[14396:408596] 任务二10
    2019-07-31 17:22:17.599931+0800 ArrayTest[14396:408597] 任务三10
    2019-07-31 17:22:17.599949+0800 ArrayTest[14396:408598] 任务一200
    2019-07-31 17:22:17.599932+0800 ArrayTest[14396:408596] 任务二20
    2019-07-31 17:22:17.600011+0800 ArrayTest[14396:408597] 任务三15
    2019-07-31 17:22:17.600252+0800 ArrayTest[14396:408598] 任务一300
    2019-07-31 17:22:17.600424+0800 ArrayTest[14396:408597] 任务三20
    2019-07-31 17:22:17.600626+0800 ArrayTest[14396:408598] 任务一400
    2019-07-31 17:22:17.600784+0800 ArrayTest[14396:408597] 任务三25
    2019-07-31 17:22:17.601275+0800 ArrayTest[14396:408596] 任务二30
    2019-07-31 17:22:17.601423+0800 ArrayTest[14396:408596] 任务二40
    2019-07-31 17:22:17.601702+0800 ArrayTest[14396:408489] -------同步barrier的任务0-------
    2019-07-31 17:22:17.601942+0800 ArrayTest[14396:408489] -------同步barrier的任务5-------
    2019-07-31 17:22:17.602155+0800 ArrayTest[14396:408489] -------同步barrier的任务10-------
    2019-07-31 17:22:17.602368+0800 ArrayTest[14396:408489] -------同步barrier的任务15-------
    2019-07-31 17:22:17.602592+0800 ArrayTest[14396:408489] -------同步barrier的任务20-------
    2019-07-31 17:22:17.602798+0800 ArrayTest[14396:408489] -------同步barrier的任务25-------
    2019-07-31 17:22:17.603012+0800 ArrayTest[14396:408489] -------同步barrier的任务30-------
    2019-07-31 17:22:17.616610+0800 ArrayTest[14396:408489] -------同步barrier的任务35-------
    2019-07-31 17:22:17.616736+0800 ArrayTest[14396:408489] 外面的任务
    2019-07-31 17:22:17.616874+0800 ArrayTest[14396:408598] 任务六0
    2019-07-31 17:22:17.616899+0800 ArrayTest[14396:408597] 任务四0
    2019-07-31 17:22:17.617111+0800 ArrayTest[14396:408598] 任务六1
    2019-07-31 17:22:17.617199+0800 ArrayTest[14396:408597] 任务四1
    2019-07-31 17:22:17.617345+0800 ArrayTest[14396:408598] 任务六2
    2019-07-31 17:22:17.617427+0800 ArrayTest[14396:408597] 任务四2
    
    再使用dispatch_barrier_async
        // 这里使用异步栅栏函数
        dispatch_barrier_async(concurrent_queue, ^{
            for (int i = 0; i < 40; i++) {
                if (i % 5 == 0) {
                    NSLog(@"-------异步barrier的任务%d-------",i);
                }
            }
        });
    

    打印结果如下:

    2019-07-31 17:25:28.130839+0800 ArrayTest[14457:413975] 任务一0
    2019-07-31 17:25:28.130846+0800 ArrayTest[14457:413977] 任务三0
    2019-07-31 17:25:28.130839+0800 ArrayTest[14457:413986] 任务二0
    2019-07-31 17:25:28.131042+0800 ArrayTest[14457:413977] 任务三5
    2019-07-31 17:25:28.131067+0800 ArrayTest[14457:413986] 任务二10
    2019-07-31 17:25:28.131043+0800 ArrayTest[14457:413975] 任务一100
    2019-07-31 17:25:28.131130+0800 ArrayTest[14457:413977] 任务三10
    2019-07-31 17:25:28.131157+0800 ArrayTest[14457:413975] 任务一200
    2019-07-31 17:25:28.131172+0800 ArrayTest[14457:413986] 任务二20
    2019-07-31 17:25:28.131238+0800 ArrayTest[14457:413977] 任务三15
    2019-07-31 17:25:28.130880+0800 ArrayTest[14457:413837] 外面的任务
    2019-07-31 17:25:28.131664+0800 ArrayTest[14457:413975] 任务一300
    2019-07-31 17:25:28.131828+0800 ArrayTest[14457:413977] 任务三20
    2019-07-31 17:25:28.131980+0800 ArrayTest[14457:413975] 任务一400
    2019-07-31 17:25:28.132137+0800 ArrayTest[14457:413977] 任务三25
    2019-07-31 17:25:28.132620+0800 ArrayTest[14457:413986] 任务二30
    2019-07-31 17:25:28.132911+0800 ArrayTest[14457:413986] 任务二40
    2019-07-31 17:25:28.133144+0800 ArrayTest[14457:413986] -------异步barrier的任务0-------
    2019-07-31 17:25:28.133334+0800 ArrayTest[14457:413986] -------异步barrier的任务5-------
    2019-07-31 17:25:28.133543+0800 ArrayTest[14457:413986] -------异步barrier的任务10-------
    2019-07-31 17:25:28.133761+0800 ArrayTest[14457:413986] -------异步barrier的任务15-------
    2019-07-31 17:25:28.133959+0800 ArrayTest[14457:413986] -------异步barrier的任务20-------
    2019-07-31 17:25:28.134183+0800 ArrayTest[14457:413986] -------异步barrier的任务25-------
    2019-07-31 17:25:28.140504+0800 ArrayTest[14457:413986] -------异步barrier的任务30-------
    2019-07-31 17:25:28.140658+0800 ArrayTest[14457:413986] -------异步barrier的任务35-------
    2019-07-31 17:25:28.140785+0800 ArrayTest[14457:413986] 任务四0
    2019-07-31 17:25:28.140788+0800 ArrayTest[14457:413977] 任务六0
    2019-07-31 17:25:28.140883+0800 ArrayTest[14457:413986] 任务四1
    2019-07-31 17:25:28.140892+0800 ArrayTest[14457:413977] 任务六1
    2019-07-31 17:25:28.140961+0800 ArrayTest[14457:413986] 任务四2
    2019-07-31 17:25:28.140987+0800 ArrayTest[14457:413977] 任务六2
    

    实现线程安全的数组

    通过上面的知识点可以知道,一个用nonatomic修饰的数组成员变量,它的线程访问是不受限制的,当然我们也已经知道用atomic修饰也并不合适,因为线程访问得到的值依然不够准确。
    那要实现线程安全的数组,该怎么办呢?使用dispatch_barrier函数可以解决

    将数组的写(插入、修改、删除)操作放进队列中dispatch_barrier函数中,这样当进行写的操作时,会先等待前面的读的任务完成后再执行写操作;而且后面的读任务也要等待dispatch_barrier中的写操作执行完成后才会被执行。

    代码

    创建一个类给它添加一个可变数组的成员变量,给这个类添加访问数组成员变量的所有方法。不多说,看代码:
    .h文件

    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface ZHMutableArray : NSObject
    
    // 读取数组
    - (NSMutableArray *)array;
    //判断是否包含对象
    - (BOOL)containsObject:(id)anObject;
    //集合元素数量
    - (NSUInteger)count;
    //获取元素
    - (id)objectAtIndex:(NSUInteger)index;
    //枚举元素
    - (NSEnumerator *)objectEnumerator;
    //插入
    - (void)insertObject:(id)anObject atIndex:(NSUInteger)index;
    //添加
    - (void)addObject:(id)anObject;
    //移除
    - (void)removeObjectAtIndex:(NSUInteger)index;
    //移除
    - (void)removeObject:(id)anObject;
    //移除
    - (void)removeLastObject;
    //替换
    - (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject;
    //获取索引
    - (NSUInteger)indexOfObject:(id)anObject;
    
    @end
    

    .m文件

    凡涉及更改数组中元素的操作,使用异步栅栏块;读取数据使用 同步+并行队列

    #import "ZHMutableArray.h"
    
    @interface ZHMutableArray()
    
    @property (nonatomic,strong)dispatch_queue_t concurrentQueue;
    @property (nonatomic,strong)NSMutableArray *arr;
    
    @end
    
    @implementation ZHMutableArray
    
    -(instancetype)init{
        self = [super init];
        if (self) {
            NSString *identifier = [NSString stringWithFormat:@"<ZHMutableArray>%p",self];
            self.concurrentQueue = dispatch_queue_create([identifier UTF8String], DISPATCH_QUEUE_CONCURRENT);
            self.arr = [NSMutableArray array];
        }
        return self;
    }
    
    - (NSMutableArray *)array
    {
        __block NSMutableArray *safeArray;
        dispatch_sync(_concurrentQueue, ^{
            safeArray = self.arr;
        });
        return safeArray;
    }
    
    - (BOOL)containsObject:(id)anObject
    {
        __block BOOL isExist = NO;
        dispatch_sync(_concurrentQueue, ^{
            isExist = [self.arr containsObject:anObject];
        });
        return isExist;
    }
    
    - (NSUInteger)count
    {
        __block NSUInteger count;
        dispatch_sync(_concurrentQueue, ^{
            count = self.arr.count;
        });
        return count;
    }
    
    - (id)objectAtIndex:(NSUInteger)index
    {
        __block id obj;
        dispatch_sync(_concurrentQueue, ^{
            if (index < [self.arr count]) {
                obj = self.arr[index];
            }
        });
        return obj;
    }
    
    - (NSEnumerator *)objectEnumerator
    {
        __block NSEnumerator *enu;
        dispatch_sync(_concurrentQueue, ^{
            enu = [self.arr objectEnumerator];
        });
        return enu;
    }
    
    - (void)insertObject:(id)anObject atIndex:(NSUInteger)index
    {
        dispatch_barrier_async(_concurrentQueue, ^{
            if (anObject && index < [self.arr count]) {
                [self.arr insertObject:anObject atIndex:index];
            }
        });
    }
    
    - (void)addObject:(id)anObject
    {
        dispatch_barrier_async(_concurrentQueue, ^{
            if(anObject){
                [self.arr addObject:anObject];
            }
        });
    }
    
    - (void)removeObjectAtIndex:(NSUInteger)index
    {
        dispatch_barrier_async(_concurrentQueue, ^{
            
            if (index < [self.arr count]) {
                [self.arr removeObjectAtIndex:index];
            }
        });
    }
    
    - (void)removeObject:(id)anObject
    {
        dispatch_barrier_async(_concurrentQueue, ^{
            [self.arr removeObject:anObject];//外边自己判断合法性
        });
    }
    
    - (void)removeLastObject
    {
        dispatch_barrier_async(_concurrentQueue, ^{
            [self.arr removeLastObject];
        });
    }
    
    - (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject
    {
        dispatch_barrier_async(_concurrentQueue, ^{
            if (anObject && index < [self.arr count]) {
                [self.arr replaceObjectAtIndex:index withObject:anObject];
            }
        });
    }
    
    - (NSUInteger)indexOfObject:(id)anObject
    {
        __block NSUInteger index = NSNotFound;
        dispatch_sync(_concurrentQueue, ^{
            for (int i = 0; i < [self.arr count]; i ++) {
                if ([self.arr objectAtIndex:i] == anObject) {
                    index = i;
                    break;
                }
            }
        });
        return index;
    }
    
    - (void)dealloc
    {
        if (_concurrentQueue) {
            _concurrentQueue = NULL;
        }
    }
    

    这样一个线程安全的数组就创建完成。

    其他

    针对网上一些博客说,使用atomic修饰的属性,在访问时会出现效率很低的情况,个人研究了下,觉得这事不靠谱,不知道读者有没有办法证明这个观点。
    看下面的代码:
    首先在.m文件中声明两个私有属性

    @property (nonatomic,strong)ZHMutableArray *array;
    @property (atomic,strong)NSMutableArray *tempAarray;
    

    然后,往两个集合加入相同数量相同的值

        self.array = [[ZHMutableArray alloc] init];
        for (int i = 0; i < 10000; i++) {
            NSString *str = [NSString stringWithFormat:@"%d",i];
            [self.array addObject:str];
        }
        
        //
        self.tempAarray = [[NSMutableArray alloc] init];
        for (int i = 0; i < 10000; i++) {
            [self.tempAarray addObject:[NSString stringWithFormat:@"%d",i]];
        }
    

    最后执行数组的访问

        // tempArray的访问
        dispatch_queue_t queue_t00 = dispatch_queue_create("dispatch_queue00", DISPATCH_QUEUE_CONCURRENT);
        dispatch_queue_t queue_t01 = dispatch_queue_create("dispatch_queue01", DISPATCH_QUEUE_CONCURRENT);
        CFAbsoluteTime currentTime0 = CFAbsoluteTimeGetCurrent();
        dispatch_apply(5000, queue_t00, ^(size_t index0) {
            NSLog(@"%@",[self.tempAarray objectAtIndex:index0]);
        });
        dispatch_apply(5000, queue_t01, ^(size_t index0) {
            NSLog(@"%@",[self.tempAarray objectAtIndex:index0+4999]);
        });
        CFAbsoluteTime totalTime0 = CFAbsoluteTimeGetCurrent() - currentTime0;
        
        // array的访问
        dispatch_queue_t queue_t10 = dispatch_queue_create("dispatch_queue10", DISPATCH_QUEUE_CONCURRENT);
        dispatch_queue_t queue_t11 = dispatch_queue_create("dispatch_queue11", DISPATCH_QUEUE_CONCURRENT);
        CFAbsoluteTime currentTime1 = CFAbsoluteTimeGetCurrent();
        dispatch_apply(5000, queue_t10, ^(size_t index1) {
            NSLog(@"%@",[self.array objectAtIndex:index1]);
        });
        dispatch_apply(5000, queue_t11, ^(size_t index1) {
            NSLog(@"%@",[self.array objectAtIndex:index1 + 4999]);
        });
        CFAbsoluteTime totalTime1 = CFAbsoluteTimeGetCurrent() - currentTime1;
        
        // 两个访问时间对比
        NSLog(@"totalTime0:%f - totalTime1:%f",totalTime0,totalTime1);
    

    打印结果如下:

    第一次:totalTime0:2.519118 - totalTime1:2.448710
    第二次:totalTime0:2.249565 - totalTime1:2.966130
    第三次:totalTime0:2.374601 - totalTime1:2.334822
    第四次:totalTime0:2.824524 - totalTime1:2.441588
    第五次:totalTime0:2.092460 - totalTime1:2.245025
    

    通过上面的数据可以看到,在执行效率上并没有想象中的那么大的差别。

    深入分析待续....

    相关文章

      网友评论

          本文标题:iOS 数组线程安全

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