Objective-C cpu cache behavior Objective-C cpu cache behavior multithreading multithreading

Objective-C cpu cache behavior


I know you are probably asking about the general case of using variables across threads (in which case the rules about using volatile and locks are the same for ObjC as it is for normal C). However, for the example code you posted the rules are a little different. (I'll be skipping over and simplifying things and using Xcode to mean both Xcode and the compiler)

self.count = 0;dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {  self.count = 5;  dispatch_async(dispatch_get_main_queue(), ^{    NSLog(@"%i", self.count);  });}

I'm going to assume self is an NSObject subclass something like this:

@interface MyClass : NSObject {    NSInteger something;}@property (nonatomic, assign) NSInteger count;@end

Objective C is a superset of C, and if you've ever done any reverse engineering of ObjC you'll know that ObjC code (sort of, not quite) gets converted into C code before it's compiled. All [self method:object] calls get converted to objc_msgSend(self, "method:", object) calls and self is a C struct with ivars and other runtime info in it.

This means this code doesn't do quite what you might expect.

-(void)doThing{   NSInteger results = something + self.count;}

Just accessing something isn't just accessing the variable but is instead doing self->something (which is why you need to get a weak reference to self when accessing an ivar in an Objective C block to avoid a retain cycle).

The second point is Objective C properties don't really exist. self.count gets turned into [self count] and self.count = 5 gets turned into [self setCount:5]. Objective C properties are just syntax sugar; convenience save you some typing and make things look a bit nicer.

If you've been using Objective C for more than a few years ago you'll remember when you had to add @synthesize propertyName = _ivarName to the @implementation for ObjC properties you declared in the header. (now Xcode does it automatically for you)

@synthesize was a trigger for Xcode to generate the setter and getter methods for you. (if you hadn't written @synthesize Xcode expected you to write the setter and getter yourself)

// Auto generated code you never see unless you reverse engineer the compiled binary-(void)setCount:(NSInteger)count{    _count = count;}-(NSInteger)count{    return _count;}

If you are worried about threading issues with self.count you are worried about 2 threads calling these methods at once (not directly accessing the same variable at once, as self.count is actually a method call not a variable).

The property definition in the header changes what code is generated (unless you implement the setter yourself).

@property (nonatomic, retain)[_count release];[count retain];_count = count;@property (nonatomic, copy)[_count release];_count = [count copy];@property (nonatomic, assign)_count = count;

TLDR

If you care about threading and want to make sure you don't read the value half way through a write happening on another thread then change nonatomic to atomic (or get rid of nonatomic as atomic is the default). Which will result in code generated something like this.

@property (atomic, assign) NSInteger count;// setter@synchronized(self) {    _count = count;}

This won't guarantee your code is thread safe, but (as long as you only access the property view it's setter and getter) should mean you avoid the possibility of reading the value during a write on another thread. More info about atomic and nonatmoic in the answers to this question.


The simplest way, and the way that is least challenging to the developer's brain, is to perform tasks on a serial dispatch queue. A serial dispatch queue, like the main queue, is a tiny single threaded island in a multi-threaded world.


You should use lock or some other synchronise mechanism to protect the shared variable. According to the documentation it said:

Another simple way to communicate information between two threads is to use a global variable, shared object, or shared block of memory. Although shared variables are fast and simple, they are also more fragile than direct messaging. Shared variables must be carefully protected with locks or other synchronization mechanisms to ensure the correctness of your code. Failure to do so could lead to race conditions, corrupted data, or crashes.

In fact the best way to protect the counter variable is to use the Atomic Operation. You can read the article: https://www.mikeash.com/pyblog/friday-qa-2011-03-04-a-tour-of-osatomic.html