2
votes

I've read several posts about atomic and write a demo to verify the thread safe, like this

@property(atomic,assign) NSInteger sum;

// then do this

for (NSInteger i = 0; i<1000; i++) {
    dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        self.sum++;
    });
}

make a property "sum" as an atomic property; and start 1000 concurrency threads to add one ;

I though the result will be 1000, but it's not, if I add a NSLock to wrap the self.sum++ , the result is 1000;

Can we explain this?

3
What result do you get?rmaddy
And why do you create the queue inside the loop instead of before the loop?rmaddy
997 998 996 not 1000, in the loop to make 1000 threadsNickYu
Move the line dispatch_queue_t queue = dispatch_queue_create... to before the loop. You must not create a new queue for each loop iteration. Only the dispatch_async should be inside the loop.rmaddy

3 Answers

6
votes

There are several layers to this.

First, a declared property is mostly just a shortcut to declaring accessor methods. Compiler synthesis of the property, which happens by default if you don't provide your own implementation, defines those methods and an instance variable to back the property.

So, this:

@property(atomic,assign) NSInteger sum;

is basically just this:

- (NSInteger) sum;
- (void) setSum:(NSInteger)value;

Synthesis of the property produces an instance variable and implementations of those methods:

@implementation ...
{
    NSUInteger _sum;
}

- (NSInteger) sum
{
    // ...
}
- (void) setSum:(NSInteger)value
{
    // ...
}

For an atomic property, the implementations of -sum and -setSum: are each guaranteed to operate such that neither can appear to interrupt the other. A call to -sum that happens "simultaneously" with a call to -setSum: will either return the value before -setSum: or the value after it, but never some frankenstein value that's partially modified or any interim value. Likewise, two simultaneous calls to -setSum: will result in _sum having the value from one or the other of those calls, but never some mix or interim value. That is the two calls will seem to have happened in a strict order, either A then B or B then A, arbitrarily.

This is easier to understand for a property with a compound type, such as NSRect. Two threads setting the property will never result in, for example, the origin from one thread and the size from another thread being stored. One or the other will "win" and the rect will be coherent. Likewise, a thread calling the getter will never see a mixed value even if it happens simultaneously with a call to the setter.

Next, accesses to the property using dot syntax (e.g. self.sum) are really just a shortcut for calling the accessors. Since there are only get and set accessors, and not any "increment" accessor, a statement like self.sum++; needs to do both, separately:

[self setSum:[self sum] + 1];

So, your statement involves first a call -sum then a call to -setSum: for each thread. There's nothing that ensures that other threads can't interleave their operations with each other. The property's atomicity doesn't prevent it. That is, thread A may get the value 5 from its call to -sum, thread B may also get the value 5 from its call to -sum, each may calculate 6 as the new value, then they both call -setSum: with the value 6. So two threads will have "incremented" the property, but it will only have increased by 1.

In short, atomicity is not thread-safety. It's a conceptual mistake to think that it is. It's just atomicity. It does prevent one kind of corruption that can happen when multiple threads simultaneously access the same property, but not all kinds.

0
votes

Try with this:

dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT);
for (NSInteger i = 0; i<1000; i++) {
    dispatch_async(queue, ^{
       NSLock *aLock = [[NSLock alloc] init];
       [aLock lock];
       self.sum++;
       [aLock unlock];
    });
}
0
votes

Another way to do this is to set a property to the value of i in the block, then immediately assign the value of that property to another variable and check that they are equal. If they are not, display the values, the expected values and the thread number that they are on. I'm doing that in a test now similar to yours now.

self.data.intPropertyCheck = myIndex;
NSInteger capturedPropertyValue = self.data.intPropertyCheck;

When using dispatch_async, Even when using a DISPATCH_QUEUE_CONCURRENT queue I haven't come across a way to prevent one thread from stomping on the property.

If the queue is the main queue, it's really a synchronous queue, but this blocks the main thread.

Since this is creating many async blocks, I've changed i to myIndex, added a name to the thread and added these values to the logging so that you can see what is happening easier in the console and can search on the index number. Here is my for loop.

int iterations;
iterations = 1000;

for (int myIndex = 0; myIndex < iterations; myIndex++ ) {
    NSLog(@"Issuing test: %d", myIndex);
    // dispatch_async(queue, ^{
    dispatch_async(queue, ^{
        NSThread *myThread = [NSThread currentThread];
        myThread.name = [NSString stringWithFormat:@"Operation - %d", myIndex];
        
        self.data.intPropertyCheck = myIndex;
        NSInteger capturedPropertyValue = self.data.intPropertyCheck;
        BOOL success = self.data.intPropertyCheck == myIndex; // By the time we get here, the value from the previous line could have already changed.
       
        NSLog(@"Test# %d. Expected value: %d", myIndex, myIndex);
        NSLog(@"Test# %d. Actual value: %ld", myIndex, (long)self.data.intPropertyCheck); // This will occasionally show a different value. Why?  Many threads attempting to change the value at once. Even if it is atomic.
        if (success) {
            NSLog(@"Test# %d. Test passed.", myIndex);
        } else {
            NSLog(@"#### Test %d ––––––––––––––––––––––––––––– error start", myIndex);
            NSLog(@"#### Test %d failed. Values do not match.", myIndex);
            NSLog(@"#### Test %d failed. Values do not match. Thread: %@", myIndex, myThread.name);
            NSLog(@"#### Test %d failed. Expected value: %d", myIndex, myIndex);
            NSLog(@"#### Test %d failed. Captured value: %ld. Current value: %ld. Expected value: %d", myIndex, (long)capturedPropertyValue, (long)self.data.intPropertyCheck, myIndex);
             NSLog(@"#### Test %d ––––––––––––––––––––––––––––– error end", myIndex);
            
            nil;
            nil;
            NSLog(@"");
            
          //  NSAssert(success, @"#### Test %d failed. Values do not match.", myIndex);
        }
    });
}

Nils are included so that you can set breakpoints on them if you wish.