您的位置:首页 > 移动开发 > IOS开发

iOS 多线程编程的安全问题

2016-12-22 16:05 239 查看
首先我们从属性说起,理解多线程为什么不安全?

多线程共享状态可以共同访问某个对象的属性(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);
}


如果想了解更多,可以自行查阅学习,此处就不再赘述。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  iOS 多线程 安全 线程