关于atomic 是否是线程安全的问题

在 Objective - C 里,atomic 特性并不能保证对象是完全线程安全的,下面从其基本原理、部分线程安全场景以及局限性来详细说明:

先看一个例子

#import <Foundation/Foundation.h>

@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 修饰的属性,在其生成的 settergetter 方法里会加锁,以此来保证同一时间只有一个线程能对该属性进行读写操作。例如下面的代码:

@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 属性进行读写操作,由于 settergetter 方法加了锁,同一时间只会有一个线程执行读写操作,避免了数据竞争的问题。

atomic 的局限性

复合操作不安全atomic 只能保证单个属性的 settergetter 方法是线程安全的,对于复合操作无法保证线程安全。例如下面的代码:

@interface MyClass : NSObject
@property (atomic, assign) NSInteger count;
@end

@implementation MyClass

- (void)incrementCount {
    // 这是一个复合操作,先读再写
    self.count = self.count + 1; 
}

@end

incrementCount 方法中,先读取 count 的值,然后加 1 再赋值回去。虽然 count 属性使用了 atomic 修饰,但其 settergetter 方法分别是线程安全的,但整个 incrementCount 操作不是原子的。在多线程环境下,可能会出现多个线程同时读取到相同的 count 值,然后各自加 1 再赋值回去,导致最终的 count 值不符合预期。

 对象的其他操作不安全atomic 仅针对属性的 settergetter 方法加锁,对于对象的其他操作(如调用对象的方法)并不能保证线程安全。例如:

@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 只是在属性的基本读写操作上提供了一定的线程安全保障,但不能保证对象在多线程环境下的完全线程安全。如果需要更全面的线程安全,还需要使用其他同步机制(如 @synchronizedNSLock 等)来保护复合操作和对象的其他操作。

atomic vs. 真正的线程安全

atomic@synchronizedGCD 串行队列NSLock
保证 setter/getter 线程安全
保证多个线程同时操作的安全性
推荐用于简单属性多个操作依赖同一数据并发任务处理多线程数据访问
性能较好稍差高效中等
### ✅ 什么是数据竞争问题? **数据竞争(Data Race)** 是指多个线程**同时访问**同一个共享变量,其中**至少有一个线程在写入**该变量,而这些访问之间**没有适当的同步机制**(如锁、原子操作等),导致程序行为不可预测。 #### ✅ 数据竞争的条件: 1. 多个线程访问**同一内存位置**; 2. 至少有一个线程在**写入**该内存; 3. 没有明确的同步机制来控制访问顺序。 #### ❌ 示例(存在数据竞争): ```cpp #include <iostream> #include <thread> int counter = 0; void increment() { for (int i = 0; i < 100000; ++i) { ++counter; // 多个线程同时修改 counter,存在数据竞争 } } int main() { std::thread t1(increment); std::thread t2(increment); t1.join(); t2.join(); std::cout << "最终 counter 值: " << counter << std::endl; return 0; } ``` **输出结果可能不是 200000**,因为 `++counter` 不是原子操作,多个线程可能同时读写导致数据丢失。 --- ### ✅ 什么是线程安全问题? **线程安全(Thread Safety)** 是指当多个线程访问某个函数或对象时,无论线程如何调度执行,都能保证**正确的行为**,不会出现数据竞争、死锁、资源泄露等问题。 #### 线程安全的关键在于: - **同步机制**:如互斥锁(mutex)、原子操作、条件变量; - **避免共享可变状态**; - **使用线程安全的数据结构**(如 `std::atomic`、`std::mutex`); --- ### ✅ 数据竞争和线程安全的关系 | 概念 | 描述 | 是否线程安全 | |------|------|---------------| | 数据竞争 | 多线程同时访问共享变量,至少一个写操作 | ❌ 不安全 | | 线程安全 | 多线程访问时行为正确,不发生数据竞争 | ✅ 安全 | --- ### ✅ 如何解决数据竞争问题? #### 方法一:使用互斥锁(`std::mutex`) ```cpp #include <iostream> #include <thread> #include <mutex> int counter = 0; std::mutex mtx; void increment() { for (int i = 0; i < 100000; ++i) { std::lock_guard<std::mutex> lock(mtx); ++counter; // 通过锁保护共享变量 } } ``` #### 方法二:使用原子变量(`std::atomic<int>`) ```cpp #include <atomic> std::atomic<int> counter(0); void increment() { for (int i = 0; i < 100000; ++i) { ++counter; // 原子操作,线程安全 } } ``` --- ### ✅ 数据竞争可能导致的问题 | 问题 | 描述 | |------|------| | 结果不可预测 | 同一程序多次运行结果不同 | | 数据损坏 | 多线程同时写入导致数据被破坏 | | 死锁 | 线程互相等待资源,程序卡死 | | 性能下降 | 编译器无法优化,频繁内存访问 | | 安全漏洞 | 多线程操作不当可能导致系统漏洞 | --- ### ✅ 线程安全的函数或类应满足的条件 1. **不使用共享可变状态**(如全局变量、静态变量); 2. **使用锁或原子操作保护共享资源**; 3. **避免竞态条件(Race Condition)**; 4. **可重入(Reentrant)或线程安全**; --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值