在阅读Apple的文档后,我尝试在Objective-C中提供属性的原子性或非原子性.为此,我创建了一个具有名和姓的Person.
Person.h
@interface Person : NSObject @property (nonatomic, strong) NSString *firstName; @property (nonatomic, strong) NSString *lastName; - (instancetype)initWithFirstName:(NSString *)fn lastName:(NSString *)ln; @end
Person.m
@implementation Person - (instancetype)initWithFirstName:(NSString *)fn lastName:(NSString *)ln { if (self = [super init]) { self.firstName = fn; self.lastName = ln; } return self; } - (NSString *)description { return [NSString stringWithFormat:@"%@ %@", self.firstName, self.lastName]; } @end
在另一个类中,我的AppDelegate,我有一个非原子属性,它是Person的一个实例.
@property (strong, nonatomic) Person *p;
在实现文件中,我创建了三个并发队列.在第一个队列中,我读取了属性,在另外两个队列中,我写了不同的person值.
根据我的理解,我可以在我的日志中输出Bob Frost或Jack Sponge,因为我声明我的属性是非原子的.但那并没有发生.我不明白为什么.我错过了什么或误解了什么吗?
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. Person *bob = [[Person alloc] initWithFirstName:@"Bob" lastName:@"Sponge"]; Person *jack = [[Person alloc] initWithFirstName:@"Jack" lastName:@"Frost"]; self.p = bob; dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT); dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT); dispatch_queue_t queue3 = dispatch_queue_create("queue3", DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue1, ^{ while (YES) { NSLog(@"%@", self.p); } }); dispatch_async(queue2, ^{ while (YES) { self.p = bob; } }); dispatch_async(queue3, ^{ while (YES) { self.p = jack; } }); return YES; }
Duncan C.. 5
具有非原子属性使得部分写入的可能性成为可能,但决不是确定的.
在Person类中,设置名字和姓氏的唯一方法是在init方法中,然后设置第一个名称,然后立即设置姓氏.设置名字和姓氏将非常接近彼此,很少有机会让另一个线程在操作之间弄乱.
此外,在运行并发操作之前,在主线程中创建Person对象.当您的当前代码运行时,对象已经存在,您不再更改其名称值,因此不存在竞争条件或具有名称值的部分写入的可能性.您只是在2个对象之间更改self.p,这些对象在创建后不会更改.
也就是说,你的代码无法预测的是什么人物在任何时刻都会在self.p中出现.您应该看到Bob Sponge和Jack Frost之间显示的值无法预测.
更好的测试是这样的:
(假设每个TestObject的x1和x2值应始终保持不变.)
@interface TestObject : NSObject @property (nonatomic, assign) int x1; @property (nonatomic, assign) int x2; @end @interface AppDelegate @property (nonatomic, strong) TestObject *thing1; @property (nonatomic, strong) TestObject *thing2; @property (nonatomic, strong) NSTimer *aTimer; @property (nonatomic, strong) NSTimer *secondTimer; @end
然后像这样的代码:
#include- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT); dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT); self.thing1 = [[TestObject alloc] init]; self.thing2 = [[TestObject alloc] init]; dispatch_async(queue1, ^ { for (int x = 0; x < 100; x++) { usleep(arc4random_uniform(50000)); //sleep for 0 to 50k microseconds int thing1Val = arc4random_uniform(10000); int thing2Val = arc4random_uniform(10000); _thing1.x1 = thing1Val; usleep(arc4random_uniform(50000)); //sleep for 0 to 50k microseconds _thing2.x1 = thing2Val; _thing1.x2 = thing1Val; //thing1's x1 and x2 should now match usleep(arc4random_uniform(50000)); //sleep for 0 to 50k microseconds _thing2.x2 = thing2Val; //And now thing2's x1 and x2 should also both match } }); //Do the same thing on queue2 dispatch_async(queue2, ^ { for (int x = 0; x < 100; x++) { usleep(arc4random_uniform(50000)); //sleep for 0 to 50k microseconds int thing1Val = arc4random_uniform(10000); int thing2Val = arc4random_uniform(10000); _thing1.x1 = thing1Val; usleep(arc4random_uniform(50000)); //sleep for 0 to 50k microseconds _thing2.x1 = thing2Val; _thing1.x2 = thing1Val; //thing1's x1 and x2 should now match usleep(arc4random_uniform(50000)); //sleep for 0 to 50k microseconds _thing2.x2 = thing2Val; //And now thing2's x1 and x2 should also both match } }); //Log the values in thing1 and thing2 every .1 second self.aTimer = [NSTimer scheduledTimerWithTimeInterval:.1 target:self selector:@selector(logThings:) userInfo:nil repeats:YES]; //After 5 seconds, kill the timer. self.secondTimer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:@selector(stopRepeatingTimer:) userInfo:nil repeats:NO]; return YES; } - (void)stopRepeatingTimer:(NSTimer *)timer { [self.aTimer invalidate]; } - (void)logThings:(NSTimer *)timer { NSString *equalString; if (_thing1.x1 == _thing1.x2) { equalString = @"equal"; } else { equalString = @"not equal"; } NSLog(@"%@ : thing1.x1 = %d, thing1.x2 = %d", equalString, _thing1.x1, _thing1.x2); if (_thing2.x1 == _thing2.x2) { equalString = @"equal"; } else { equalString = @"not equal"; } NSLog(@"%@ : thing2.x1 = %d, thing2.x2 = %d", equalString, _thing2.x1, _thing2.x2); }
在上面的代码中,每个队列都会创建一系列随机值,并将一对对象的x1和x2属性设置为重复循环中的随机值.它延迟了设置每个对象的x1和x2属性之间的小的随机间隔.该延迟模拟后台任务需要一些时间来完成应该是原子的工作.它还引入了一个窗口,其中另一个线程可以在当前线程能够设置第二个值之前更改第二个值.
如果你运行上面的代码,你几乎肯定会发现thing1和thing2的x1和x2值有时是不同的.
上面的代码对原子属性没有帮助.您需要在设置每个对象的x1和x2属性之间声明某种锁(可能使用该@synchronized
指令).
(请注意,我在论坛编辑器中将上面的代码组合在一起.我没有尝试编译它,更不用说调试它了.毫无疑问有一些错别字.)
(注2,编辑我的代码的人:代码格式是风格和个人品味的问题.我使用"Allman缩进"的变体.我欣赏错别字修正,但我鄙视K&R风格的缩进.不要强加您的风格我的代码.
具有非原子属性使得部分写入的可能性成为可能,但决不是确定的.
在Person类中,设置名字和姓氏的唯一方法是在init方法中,然后设置第一个名称,然后立即设置姓氏.设置名字和姓氏将非常接近彼此,很少有机会让另一个线程在操作之间弄乱.
此外,在运行并发操作之前,在主线程中创建Person对象.当您的当前代码运行时,对象已经存在,您不再更改其名称值,因此不存在竞争条件或具有名称值的部分写入的可能性.您只是在2个对象之间更改self.p,这些对象在创建后不会更改.
也就是说,你的代码无法预测的是什么人物在任何时刻都会在self.p中出现.您应该看到Bob Sponge和Jack Frost之间显示的值无法预测.
更好的测试是这样的:
(假设每个TestObject的x1和x2值应始终保持不变.)
@interface TestObject : NSObject @property (nonatomic, assign) int x1; @property (nonatomic, assign) int x2; @end @interface AppDelegate @property (nonatomic, strong) TestObject *thing1; @property (nonatomic, strong) TestObject *thing2; @property (nonatomic, strong) NSTimer *aTimer; @property (nonatomic, strong) NSTimer *secondTimer; @end
然后像这样的代码:
#include- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT); dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT); self.thing1 = [[TestObject alloc] init]; self.thing2 = [[TestObject alloc] init]; dispatch_async(queue1, ^ { for (int x = 0; x < 100; x++) { usleep(arc4random_uniform(50000)); //sleep for 0 to 50k microseconds int thing1Val = arc4random_uniform(10000); int thing2Val = arc4random_uniform(10000); _thing1.x1 = thing1Val; usleep(arc4random_uniform(50000)); //sleep for 0 to 50k microseconds _thing2.x1 = thing2Val; _thing1.x2 = thing1Val; //thing1's x1 and x2 should now match usleep(arc4random_uniform(50000)); //sleep for 0 to 50k microseconds _thing2.x2 = thing2Val; //And now thing2's x1 and x2 should also both match } }); //Do the same thing on queue2 dispatch_async(queue2, ^ { for (int x = 0; x < 100; x++) { usleep(arc4random_uniform(50000)); //sleep for 0 to 50k microseconds int thing1Val = arc4random_uniform(10000); int thing2Val = arc4random_uniform(10000); _thing1.x1 = thing1Val; usleep(arc4random_uniform(50000)); //sleep for 0 to 50k microseconds _thing2.x1 = thing2Val; _thing1.x2 = thing1Val; //thing1's x1 and x2 should now match usleep(arc4random_uniform(50000)); //sleep for 0 to 50k microseconds _thing2.x2 = thing2Val; //And now thing2's x1 and x2 should also both match } }); //Log the values in thing1 and thing2 every .1 second self.aTimer = [NSTimer scheduledTimerWithTimeInterval:.1 target:self selector:@selector(logThings:) userInfo:nil repeats:YES]; //After 5 seconds, kill the timer. self.secondTimer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:@selector(stopRepeatingTimer:) userInfo:nil repeats:NO]; return YES; } - (void)stopRepeatingTimer:(NSTimer *)timer { [self.aTimer invalidate]; } - (void)logThings:(NSTimer *)timer { NSString *equalString; if (_thing1.x1 == _thing1.x2) { equalString = @"equal"; } else { equalString = @"not equal"; } NSLog(@"%@ : thing1.x1 = %d, thing1.x2 = %d", equalString, _thing1.x1, _thing1.x2); if (_thing2.x1 == _thing2.x2) { equalString = @"equal"; } else { equalString = @"not equal"; } NSLog(@"%@ : thing2.x1 = %d, thing2.x2 = %d", equalString, _thing2.x1, _thing2.x2); }
在上面的代码中,每个队列都会创建一系列随机值,并将一对对象的x1和x2属性设置为重复循环中的随机值.它延迟了设置每个对象的x1和x2属性之间的小的随机间隔.该延迟模拟后台任务需要一些时间来完成应该是原子的工作.它还引入了一个窗口,其中另一个线程可以在当前线程能够设置第二个值之前更改第二个值.
如果你运行上面的代码,你几乎肯定会发现thing1和thing2的x1和x2值有时是不同的.
上面的代码对原子属性没有帮助.您需要在设置每个对象的x1和x2属性之间声明某种锁(可能使用该@synchronized
指令).
(请注意,我在论坛编辑器中将上面的代码组合在一起.我没有尝试编译它,更不用说调试它了.毫无疑问有一些错别字.)
(注2,编辑我的代码的人:代码格式是风格和个人品味的问题.我使用"Allman缩进"的变体.我欣赏错别字修正,但我鄙视K&R风格的缩进.不要强加您的风格我的代码.
属性是atomic
指原子执行读操作和写操作执行的所有操作。(这完全独立于两个独立属性之间的一致性,如您的示例所示,这不能简单地通过添加来实现(atomic)
。)
这在两种情况下尤其重要:
对于对象指针,[_property release]; [newValue retain]; _property = newValue
ARC在存储新值时执行的隐式操作以及在value = _property; [value retain];
加载值时发生的隐式操作。
大型数据类型,其实际值无法原子加载/存储,无论保留/释放语义如何。
这是一个说明两个潜在问题的示例:
typedef struct { NSUInteger x; NSUInteger xSquared; // cached value of x*x } Data; @interface Producer : NSObject @property (nonatomic) Data latestData; @property (nonatomic) NSObject *latestObject; @end @implementation Producer - (void)startProducing { // Produce new Data structs. dispatch_async(dispatch_get_global_queue(0, 0), ^{ for (NSUInteger x = 0; x < NSUIntegerMax; x++) { Data newData; newData.x = x; newData.xSquared = x * x; // Since the Data struct is too large for a single store, // the setter actually updates the two fields separately. self.latestData = newData; } }); // Produce new objects. dispatch_async(dispatch_get_global_queue(0, 0), ^{ while (true) { // Release the previous value; retain the new value. self.latestObject = [NSObject new]; } }); [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(logStatus) userInfo:nil repeats:YES]; } - (void)logStatus { // Implicitly retain the current object for our own uses. NSObject *o = self.latestObject; NSLog(@"Latest object: %@", o); // Validate the consistency of the data. Data latest = self.latestData; NSAssert(latest.x * latest.x == latest.xSquared, @"WRONG: %lu^2 != %lu", latest.x, latest.xSquared); NSLog(@"Latest data: %lu^2 = %lu", latest.x, latest.xSquared); } @end int main(int argc, const char * argv[]) { @autoreleasepool { [[Producer new] startProducing]; [[NSRunLoop mainRunLoop] run]; } return 0; }
使用nonatomic
,对于object属性,您偶尔会遇到EXC_BAD_ACCESS崩溃,并记录如下消息:
AtomicTest [2172:57275]最新对象:
objc [2172]:NSObject对象0x100c04a00已释放,但已经释放;中断objc_overrelease_during_dealloc_error进行调试
对于Data结构,断言有时会失败:
AtomicTest [2240:59304] ***-[Producer logStatus]中的断言失败,main.m:58
AtomicTest [2240:59304] ***由于未捕获的异常'NSInternalInconsistencyException'而终止应用程序,原因:'错误:55937112 ^ 2 != 3128960610774769'
(请注意xSquared
,3128960610774769 的值实际上是55937113 2而不是55937112 2。)
制作属性(atomic)
而不是(nonatomic)
避免这两个问题,但代价是执行速度稍慢。
旁注:由于没有原子属性的概念,即使在Swift中也会出现相同的问题:
typedef struct { NSUInteger x; NSUInteger xSquared; // cached value of x*x } Data; @interface Producer : NSObject @property (nonatomic) Data latestData; @property (nonatomic) NSObject *latestObject; @end @implementation Producer - (void)startProducing { // Produce new Data structs. dispatch_async(dispatch_get_global_queue(0, 0), ^{ for (NSUInteger x = 0; x < NSUIntegerMax; x++) { Data newData; newData.x = x; newData.xSquared = x * x; // Since the Data struct is too large for a single store, // the setter actually updates the two fields separately. self.latestData = newData; } }); // Produce new objects. dispatch_async(dispatch_get_global_queue(0, 0), ^{ while (true) { // Release the previous value; retain the new value. self.latestObject = [NSObject new]; } }); [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(logStatus) userInfo:nil repeats:YES]; } - (void)logStatus { // Implicitly retain the current object for our own uses. NSObject *o = self.latestObject; NSLog(@"Latest object: %@", o); // Validate the consistency of the data. Data latest = self.latestData; NSAssert(latest.x * latest.x == latest.xSquared, @"WRONG: %lu^2 != %lu", latest.x, latest.xSquared); NSLog(@"Latest data: %lu^2 = %lu", latest.x, latest.xSquared); } @end int main(int argc, const char * argv[]) { @autoreleasepool { [[Producer new] startProducing]; [[NSRunLoop mainRunLoop] run]; } return 0; }