Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Evidence of atomic / nonatomic in Objective-C

After reading Apple's documentation, I try to put in evidence atomicity or non-atomicity of a property in Objective-C. To do this I create a class Person which has first and last name.

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

In another class, here my AppDelegate, I have a nonatomic property which is an instance of Person.

@property (strong, nonatomic) Person *p;

In the implementation file, I create three concurrent queues. In the first queue I read the property, in two other queues I write different values of person.

From what I understand, I could have Bob Frost or Jack Sponge output in my log, since I declared my property as nonatomic. But that didn't happened. I don't understand why. Am I missing something or misunderstanding something?

- (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;
}
like image 236
bob Avatar asked Dec 20 '15 19:12

bob


People also ask

What is atomic Nonatomic in Objective-C?

In Objective-C the implementation of an atomic property allows properties to be safely read and written from different threads. For nonatomic properties, the underlying pointer of a read value could be released when a new value is being written at the same time.

What is the difference between atomic and nonatomic synthesized properties?

Atomic means only one thread accesses the variable (static type). Atomic is thread-safe, but it is slow. Nonatomic means multiple threads access the variable (dynamic type). Nonatomic is thread-unsafe, but it is fast.

What is the difference between atomic and non atomic synthesized properties in Swift?

Defining a property as atomic should guarantee that it can be safely read and written from different threads. Non-atomic properties have no guarantee regarding the returned value, but they come with enhanced speed of accessing these properties. Note: Property atomicity is not synonymous with an object's thread safety.

Why do we use atomic and non atomic and what is default Behaviour?

Atomic:- is the default behavior. it will ensure the present process is completed by the CPU, before another process accesses the variable.it is not fast, as it ensures the process is completed entirelyNon-Atomic: - is NOT the default behavior.


2 Answers

Having non-atomic properties makes the possibility of partial writes possible, but by no means certain.

In your Person class the only way you are setting first and last names is in the init method, and then you set the first name and then the last name immediately after. Setting the first name and last name will occur VERY close to each other, with little chance for another thread to mess things up between operations.

Furthermore, you create your Person objects in the main thread, before you running concurrent operations. By the time your current code runs, the objects already exist and you no longer change their name values, so there's no chance of a race condition or a partial write with name values. You are simply changing self.p between 2 objects that don't change once they are created.

That said, what IS unpredictable about your code is what person object will be in self.p at any instant. You should see the values displayed alternate between Bob Sponge and Jack Frost unpredictably.

A better test would be something like this:

(Assume each TestObject's x1 and x2 values should always be kept the same.)

@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

And then code like this:

#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);
 }

In the code above, each queue creates a series of random values, and sets both the x1 and x2 properties of a couple of objects to those random values in a repeating loop. It delays for a small random interval between setting the x1 and x2 property of each object. That delay simulates a background task taking some amount of time to finish work that should be atomic. It also introduces a window where another thread could change the second value before the current thread is able to set the second value.

If you run the code above you will almost certainly find that the x1 and x2 values of thing1 and thing2 are sometimes different.

The code above would not be helped by atomic properties. You would need to assert a lock of some sort between setting the x1 and x2 property of each object (perhaps using the @synchronized directive).

(Note that I banged the code above together in the forum editor. I haven't tried to compile it, much less debug it. There are doubtless a few typos.)

(Note 2, to the person who edited my code: Code formatting is a matter of style and personal taste. I use a variation on "Allman indentation." I appreciate the typos corrections, but I despise K&R style indentation. Don't impose your style on my code.

like image 172
Duncan C Avatar answered Sep 25 '22 13:09

Duncan C


A property being atomic means that all actions performed by a read, and all actions performed by a write, are done atomically. (This is completely independent of consistency between two separate properties, as in your example, which cannot be achieved simply by adding (atomic).)

This matters particularly in two cases:

  1. For object pointers, the implicit [_property release]; [newValue retain]; _property = newValue operations that ARC performs when you store a new value, and the implicit value = _property; [value retain]; which happens when you load the value.

  2. Large datatypes whose actual values can't be atomically loaded/stored, regardless of retain/release semantics.

Here is an example which illustrates both potential problems:

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

With nonatomic, for the object property, you'll occasionally get EXC_BAD_ACCESS crashes, and log messages like this:

AtomicTest[2172:57275] Latest object: <NSObject: 0x100c04a00>
objc[2172]: NSObject object 0x100c04a00 overreleased while already deallocating; break on objc_overrelease_during_dealloc_error to debug

And for the Data struct, the assertion will occasionally fail:

AtomicTest[2240:59304] *** Assertion failure in -[Producer logStatus], main.m:58
AtomicTest[2240:59304] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'WRONG: 55937112^2 != 3128960610774769'

(Notice that the value of xSquared, 3128960610774769, is actually 559371132 rather than 559371122.)

Making the properties (atomic) rather than (nonatomic) avoids both of these problems, at the cost of slightly slower execution.


Side note: the same problem occurs even in Swift, because there is no notion of atomic properties:

class Object { }
var obj = Object()

dispatch_async(dispatch_get_global_queue(0, 0)) {
    while true {
        obj = Object()
    }
}

while true {
    // This sometimes crashes, and sometimes deadlocks
    let o = obj
    print("Current object: \(o)")
}
like image 31
jtbandes Avatar answered Sep 24 '22 13:09

jtbandes