iOS 多线程编程的安全问题

来源:互联网 发布:淘宝二手手机3c认证 编辑:程序博客网 时间:2024/05/22 06:54

首先我们从属性说起,理解多线程为什么不安全?

多线程共享状态可以共同访问某个对象的属性(property),我们都知道给property加上atomic attribute之后,一定程度上可以保证多线程安全:

@property (copy, atomic) NSString *userName;

这样写,多线程就真的安全吗?

@property (assign, atomic) int count;- (void)testAtomic {    NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(thread1Action) object:self];    thread1.name = @"thread1";        NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(thread2Action) object:self];    thread2.name = @"thread2";        [thread1 start];    [thread2 start];}- (void)thread1Action {    for (int i = 0; i < 10000; i++) {        self.count += 1;        NSLog(@"%@:%d",[NSThread currentThread].name, self.count);    }}- (void)thread2Action {    for (int i = 0; i < 10000; i++) {        self.count += 1;        NSLog(@"%@:%d",[NSThread currentThread].name, self.count);    }}

经过测试,即使将count声明为atomic,也很难保证最后的结果是20000。

愿意就是 self.count += 1这句并不是原子操作,我们声明count为atomic,意味着count的setter和getter方法都是原子操作。程序在执行这句代码的时候,其实至少包含了读,写操作,当前线程写的时候,另一个线程可能已经写了好多次数据了,导致最后的结果值小于预期值。这种场景我们就可以任务是多线程不安全的。

@property (strong, atomic) NSArray *array;- (void)testAtomic {    NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(thread1Action) object:self];    thread1.name = @"thread1";        NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(thread2Action) object:self];    thread2.name = @"thread2";        [thread1 start];    [thread2 start];}- (void)thread1Action {    for (int i = 0; i < 1000000; i++) {        if (i % 2 == 0) {            self.array = @[@"1", @2, @YES];        } else {            self.array = @[@"1"];        }    }    NSLog(@"%@:%@",[NSThread currentThread].name, self.array);}- (void)thread2Action {    for (int i = 0; i < 1000000; i++) {        if ([self.array count] >= 2) {            [self.array objectAtIndex:1];        }    }    NSLog(@"%@:%@",[NSThread currentThread].name, self.array);}

上面我们对集合做了测试,同样声明为atomic,虽然我们在访问数组元素之前,做了count判断,但thread2依然很容易crash。

通过测试,我们发现即使声明为atomic也不能保证多线程安全,其作用只是给setter和getter方法加了个锁,只能保证代码进入setter或getter方法内存时安全的,一旦出了存取方法就不在起作用,所以atomic属性和使用property的多线程并没有直接联系。另外,atomic由于加锁也会带来一些性能损耗,所以我们在声明属性的时候,一般声明为nonatomic,在需要做多线程安全的场景,需要我们去额外加锁做同步。我们平常APP出现莫名其妙难以重现的多线程crash多是这一类,所以我们在多线程的场景下访问这类内存区域时要多加小心。


那么,平时我们做才能保证多线程安全呢?

简单点,只要保证原子性就可以。原子性可以保证代码串行执行,保证在执行的过程中,不会有其他线程介入。

@property (strong, atomic) NSArray *array;@property (strong, nonatomic) NSLock *lock;- (void)testAtomic {        self.lock = [[NSLock alloc] init];        NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(thread1Action) object:self];    thread1.name = @"thread1";        NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(thread2Action) object:self];    thread2.name = @"thread2";        [thread1 start];    [thread2 start];}- (void)thread1Action {    [self.lock lock];    for (int i = 0; i < 1000000; i++) {        if (i % 2 == 0) {            self.array = @[@"1", @2, @YES];        } else {            self.array = @[@"1"];        }        NSLog(@"%@:%@",[NSThread currentThread].name, self.array);    }    [self.lock unlock];}- (void)thread2Action {    [self.lock lock];    for (int i = 0; i < 1000000; i++) {        if ([self.array count] >= 2) {            [self.array objectAtIndex:1];        }        NSLog(@"%@:%@",[NSThread currentThread].name, self.array);    }    [self.lock unlock];}


通过加锁的方式可实现原子性操作。

iOS常用的加锁方式有一下几种:

  • @synchronized(token)
  • NSLock
  • dispath_semaphore_t
上面里示例就是使用NSLock,当然还有什么条件锁,递归锁等等,大家可自行学习,以下是使用其余两种的示例:

- (void)testDispathSemaphore {    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);    dispatch_semaphore_t derma = dispatch_semaphore_create(1);    NSMutableArray *array = [NSMutableArray array];    for (int i = 0; i < maxCount; i++) {        dispatch_async(globalQueue, ^{            dispatch_semaphore_wait(derma, DISPATCH_TIME_FOREVER);//-1            [array addObject:@(i)];            dispatch_semaphore_signal(derma);//+1        });    }    NSLog(@"%@", array);}- (void)testSynchronized {    dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);    NSMutableArray *array = [NSMutableArray array];    for (int i = 0; i < maxCount; i++) {        dispatch_async(globalQueue, ^{            @synchronized (array) {                [array addObject:@(i)];            }        });    }    NSLog(@"%@", array);}

如果想了解更多,可以自行查阅学习,此处就不再赘述。



0 0