ios - Objective-C 中原子/非原子的证据

标签 ios objective-c macos atomic

看完Apple's documentation ,我尝试在 Objective-C 中证明属性的原子性或非原子性。为此,我创建了一个具有名字和姓氏的 Person 类。

人.h

@interface Person : NSObject
@property (nonatomic, strong) NSString *firstName;
@property (nonatomic, strong) NSString *lastName;

- (instancetype)initWithFirstName:(NSString *)fn lastName:(NSString *)ln;
@end

人.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 FrostJack Sponge,因为我将我的属性声明为 nonatomic。但那并没有发生。我不明白为什么。我是否遗漏了什么或误解了什么?

- (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;
}

最佳答案

具有非原子属性使得部分写入的可能性成为可能,但绝不是确定的。

在您的 Person 类中,设置名字和姓氏的唯一方法是在 init 方法中,然后设置名字,然后紧接着设置姓氏。设置名字和姓氏将非常接近彼此,另一个线程几乎没有机会在操作之间搞砸。

此外,在运行并发操作之前,您在主线程中创建了您的 Person 对象。当您当前的代码运行时,对象已经存在并且您不再更改它们的名称值,因此不会出现竞争条件或使用名称值进行部分写入。您只是在两个对象之间更改 self.p,这两个对象一旦创建就不会更改。

也就是说,您的代码不可预测的是 person 对象随时会在 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 <stdlib.h>
- (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 风格的缩进。不要'将您的风格强加于我的代码。

关于ios - Objective-C 中原子/非原子的证据,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34384807/

相关文章:

mysql - 错误 2002 (HY000) : Can't connect to local MySQL server through socket '/Applications/XAMPP/xamppfiles/var/mysql/mysql.sock' (2) - XAMPP Mac OSX

ios - 如何在单击 TableViewCell 中的按钮时增加标签和单元格大小。

ios - Swift - 存储和检索数据

iphone - 引用计数的对象在释放后使用

objective-c - 在 NSString 的一部分周围绘制方框

c++ - 在事件的 OS X 桌面上保留一个窗口

macos - 如何在MAC OS中安装sha1sum?

ios - 向 iOS 用户显示企业应用程序正在安装

ios - 引用类时了解 iOS 文档 (cocoa touch)

ios - 通过桥接头从 SWIFT 调用 objective-c 函数