Atomic Counters in Objective-C

Atomic counters are useful for managing state across multiple threads. In this example, we’ll look at using atomic operations to safely increment a counter accessed by multiple threads.

#import <Foundation/Foundation.h>
#import <libkern/OSAtomic.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // We'll use an atomic 64-bit integer to represent our counter
        __block int64_t ops = 0;
        
        // We'll use a dispatch group to wait for all threads to finish
        dispatch_group_t group = dispatch_group_create();
        
        // We'll start 50 threads that each increment the counter exactly 1000 times
        for (int i = 0; i < 50; i++) {
            dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                for (int c = 0; c < 1000; c++) {
                    // To atomically increment the counter we use OSAtomicIncrement64
                    OSAtomicIncrement64(&ops);
                }
            });
        }
        
        // Wait until all the threads are done
        dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
        
        // Here no threads are writing to 'ops', but it's safe to read the value
        NSLog(@"ops: %lld", ops);
    }
    return 0;
}

In this Objective-C version:

  1. We use int64_t as our counter type, which is a 64-bit integer.

  2. Instead of Go’s WaitGroup, we use a Grand Central Dispatch (GCD) group (dispatch_group_t) to wait for all threads to complete.

  3. We replace Go’s goroutines with GCD’s dispatch_group_async, which allows us to dispatch blocks of code to be executed concurrently.

  4. For atomic incrementation, we use OSAtomicIncrement64 from the libkern/OSAtomic.h header. This function atomically increments the 64-bit integer.

  5. We use NSLog for output instead of Go’s fmt.Println.

When you run this program, you should see:

ops: 50000

We expect to get exactly 50,000 operations. If we had used a non-atomic integer and incremented it with regular addition, we’d likely get a different number, changing between runs, because the threads would interfere with each other.

Note that in more recent versions of iOS and macOS, Apple recommends using stdatomic.h for atomic operations. However, OSAtomic functions are still widely used and are more similar to the Go example in terms of API.

Next, we could look at other synchronization primitives in Objective-C, such as locks or semaphores, for managing state across threads.