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,当然还有什么条件锁,递归锁等等,大家可自行学习,以下是使用其余两种的示例:
如果想了解更多,可以自行查阅学习,此处就不再赘述。
多线程共享状态可以共同访问某个对象的属性(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开发--iOS多线程操作时一些要注意的安全问题
- iOS 多线程~安全问题
- iOS多线程_02_多线程的安全问题
- iOS多线程全套:线程安全问题,GCD的使用,NSOperation的使用
- 多线程编程之一——问题提出(转载)
- 多线程编程之一——问题提出
- 【原创】关于C#多线程安全问题的讨论
- java高级多线程编程--关于线程的停止问题
- linux下 多线程编程 哲学家就餐问题
- Flash网络编程安全沙箱问题隆重解决
- 浅谈Windows多线程编程几个常见问题
- 多线程编程之一——问题提出
- java高级多线程编程--关于线程的停止问题
- 全面了解 Cookie的传递流程、编程实现及安全问题
- 全面了解Cookie的传递流程、编程实现及安全问题
- (急求急求!!!!!!!!!!!!!!!)多线程编程:哲学家问题
- 多线程编程之一——问题提出
- 全面了解 Cookie的传递流程、编程实现及安全问题
- java高级多线程编程--关于线程的停止问题
- 标 题: Solaris2.4 多线程编程指南5--安全和不安全的接口函?