614,IOS数组线程安全(面试点:在数组的地方,添加,删除,替换,插入都用栅栏函数)

概述

为什么会有数组的线程安全问题?
对于可变的集合(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

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

  • 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 

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:@"%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;
    }
}

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

你可能感兴趣的:(614,IOS数组线程安全(面试点:在数组的地方,添加,删除,替换,插入都用栅栏函数))