在 Objective - C 里,atomic
特性并不能保证对象是完全线程安全的,下面从其基本原理、部分线程安全场景以及局限性来详细说明:
先看一个例子
#import
@interface MyClass : NSObject
@property (atomic, assign) NSInteger count;
@end
@implementation MyClass
- (void)incrementCount {
self.count = self.count + 1;
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyClass *myObject = [[MyClass alloc] init];
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.example.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
// 创建多个线程同时调用 incrementCount 方法
for (int i = 0; i < 1000; i++) {
dispatch_async(concurrentQueue, ^{
[myObject incrementCount];
});
}
// 等待所有任务完成
dispatch_barrier_sync(concurrentQueue, ^{
NSLog(@"Final count: %ld", (long)myObject.count);
});
}
return 0;
}
多次执行结果
Final count: 996
Final count: 995
Final count: 991
Final count: 1000
Final count: 987
也就是说,结果是不确定,大概率不符合预期(≠1000),那么很显然多线程多操下,atomic并没有保证线程安全
咱们做如下修改,其他代码不变
@property (nonatomic, assign) NSInteger count;
多次执行结果
Final count: 561
Final count: 765
Final count: 567
Final count: 669
Final count: 720
比原来少了,也就是说atomic确实带来了一定的安全效果,只不过不完全保证
atomic
基本原理atomic
是属性声明时的一个特性,使用 atomic
修饰的属性,在其生成的 setter
和 getter
方法里会加锁,以此来保证同一时间只有一个线程能对该属性进行读写操作。例如下面的代码:
@interface MyClass : NSObject
@property (atomic, strong) NSString *myString;
@end
@implementation MyClass
// 编译器自动生成的类似 setter 方法,伪代码示意
- (void)setMyString:(NSString *)myString {
@synchronized(self) {
_myString = myString;
}
}
// 编译器自动生成的类似 getter 方法,伪代码示意
- (NSString *)myString {
@synchronized(self) {
return _myString;
}
}
@end
在单纯的属性读写操作中,atomic
能够在一定程度上保证线程安全。比如多个线程同时对 myString
属性进行读写操作,由于 setter
和 getter
方法加了锁,同一时间只会有一个线程执行读写操作,避免了数据竞争的问题。
atomic
的局限性复合操作不安全:atomic
只能保证单个属性的 setter
和 getter
方法是线程安全的,对于复合操作无法保证线程安全。例如下面的代码:
@interface MyClass : NSObject
@property (atomic, assign) NSInteger count;
@end
@implementation MyClass
- (void)incrementCount {
// 这是一个复合操作,先读再写
self.count = self.count + 1;
}
@end
在 incrementCount
方法中,先读取 count
的值,然后加 1 再赋值回去。虽然 count
属性使用了 atomic
修饰,但其 setter
和 getter
方法分别是线程安全的,但整个 incrementCount
操作不是原子的。在多线程环境下,可能会出现多个线程同时读取到相同的 count
值,然后各自加 1 再赋值回去,导致最终的 count
值不符合预期。
对象的其他操作不安全:atomic
仅针对属性的 setter
和 getter
方法加锁,对于对象的其他操作(如调用对象的方法)并不能保证线程安全。例如:
@interface MyClass : NSObject
@property (atomic, strong) NSMutableArray *myArray;
@end
@implementation MyClass
- (void)addObjectToArray:(id)object {
// 这里对 myArray 进行操作,不是 setter 和 getter 方法,atomic 无法保证安全
[self.myArray addObject:object];
}
@end
在 addObjectToArray
方法中,虽然 myArray
属性使用了 atomic
修饰,但对 myArray
调用 addObject:
方法时并没有加锁,多个线程同时调用该方法可能会导致数据不一致的问题。
综上所述,atomic
只是在属性的基本读写操作上提供了一定的线程安全保障,但不能保证对象在多线程环境下的完全线程安全。如果需要更全面的线程安全,还需要使用其他同步机制(如 @synchronized
、NSLock
等)来保护复合操作和对象的其他操作。
atomic
vs. 真正的线程安全atomic |
@synchronized |
GCD 串行队列 |
NSLock |
|
---|---|---|---|---|
保证 setter/getter 线程安全 | ✅ | ✅ | ✅ | ✅ |
保证多个线程同时操作的安全性 | ❌ | ✅ | ✅ | ✅ |
推荐用于 | 简单属性 | 多个操作依赖同一数据 | 并发任务处理 | 多线程数据访问 |
性能 | 较好 | 稍差 | 高效 | 中等 |