OSAtomic原子操作

释放双眼,带上耳机,听听看~!

并发编程一个主要问题就是如何同步数据。同步数据的方式有很多种,这里我们介绍一下libkern/OSAtomic.h。这个头文件包含是大量关于原子操作和同步操作的函数,如果要对数据进行同步操作,这里面的函数可以作为我们的首选项。不同平台这些函数的实现是自定义的。另外,它们是线程安全的。

需要注意的是,传递给这些函数的所有地址都必须是“自然对齐”的,例如int32_t *指针必须是32位对齐的(地址的低位2个bit为0),int64_t *指针必须是64位对齐的(低3位为0)。

这些原子函数的一些版本整合了内存屏障(memory barriers),而另一些则没有。在诸如PPC这样的弱有序(weakly-ordered)架构中,Barriers严格限制了内存访问顺序。所有出现在barriers之前的加载和存储操作完成后,才会运行barriers之后的加载和存储操作。

在单处理器系统中,barriers操作通常是一个空操作。在多处理器系统中,barriers在某些平台上可能是相当昂贵的操作,如PPC。

大多数代码都应该使用barrier函数来确保在线程间共享的内存是正确同步的。例如,如果我们想要初始化一个共享的数据结构,然后自动增加某个变量值来标识初始化操作完成,则我们必须使用OSAtomicIncrement32Barrier来确保数据结构的存储操作在变量自动增加前完成。

同样的,该数据结构的消费者也必须使用OSAtomicIncrement32Barrier,以确保在自动递增变量值之后再去加载这些数据。另一方面,如果我们只是简单地递增一个全局计数器,那么使用OSAtomicIncrement32会更安全且可能更快。

如果不能确保我们使用的是哪个版本,则使用barrier变量以保证是安全的。

另外,自旋锁和队列操作总是包含一个barrier。

这个头文件中的函数主要可以分为以下几类

内存屏障(Memory barriers)

内存屏障的概念如上所述,它是一种屏障和指令类,可以让CPU或编译器强制将barrier之前和之后的内存操作分开。CPU采用了一些可能导致乱序执行的性能优化。在单个线程的执行中,内存操作的顺序一般是悄无声息的,但是在并发编程和设备驱动程序中就可能出现一些不可预知的行为,除非我们小心地去控制。排序约束的特性是依赖于硬件的,并由架构的内存顺序模型来定义。一些架构定义了多种barrier来执行不同的顺序约束。

OSMemoryBarrier()函数就是用来设置内存屏障,它即可以用于读操作,也可以用于写操作。

示例代码:

// 代码来自ReactiveCocoa:RACDisposable类
	
- (id)initWithBlock:(void (^)(void))block {
    NSCParameterAssert(block != nil);
    
    self = [super init];
    if (self == nil) return nil;
    
    _disposeBlock = (void *)CFBridgingRetain([block copy]);
    OSMemoryBarrier();
    
    return self;
}

自旋锁(Spinlocks)

自旋锁是在多处理器系统(SMP)上为保护一段关键代码的执行或者关键数据的一种保护机制,是实现synchronization的一种手段。

libkern/OSAtomic.h中包含了三个关于自旋锁的函数:OSSpinLockLockOSSpinLockTryOSSpinLockUnlock

示例代码:

// 代码来自ReactiveCocoa:RACCompoundDisposable类
	
- (void)dispose {
#if RACCompoundDisposableInlineCount
    RACDisposable *inlineCopy[RACCompoundDisposableInlineCount];
#endif
    
    CFArrayRef remainingDisposables = NULL;
    
    OSSpinLockLock(&_spinLock);
    {
        _disposed = YES;
        
#if RACCompoundDisposableInlineCount
        for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) {
            inlineCopy[i] = _inlineDisposables[i];
            _inlineDisposables[i] = nil;
        }
#endif
        
        remainingDisposables = _disposables;
        _disposables = NULL;
    }
    OSSpinLockUnlock(&_spinLock);
    
#if RACCompoundDisposableInlineCount
    // Dispose outside of the lock in case the compound disposable is used
    // recursively.
    for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) {
        [inlineCopy[i] dispose];
    }
#endif
    
    if (remainingDisposables == NULL) return;
    
    CFIndex count = CFArrayGetCount(remainingDisposables);
    CFArrayApplyFunction(remainingDisposables, CFRangeMake(0, count), &disposeEach, NULL);
    CFRelease(remainingDisposables);
}

原子队列操作

队列操作主要包含两类:

  1. 不加锁的FIFO入队和出队原子操作,包含OSAtomicFifoDequeueOSAtomicFifoEnqueue两个函数
  2. 不加锁的LIFO入队和出队原子操作,包含OSAtomicDequeueOSAtomicEnqueue两个函数。这两个函数是线程安全的,对有潜在精确要求的代码来说,这会是强大的构建方式。

比较和交换

这组函数可以细分为三组函数:

  1. OSAtomicCompareAndSwap**[Barrier](type __oldValue, type __newValue, volatile type *__theValue):这组函数用于比较__oldValue是否与__theValue指针指向的内存位置的值匹配,如果匹配,则将__newValue的值存储到__theValue指向的内存位置。可以根据需要使用barrier版本。
  2. OSAtomicTestAndClear/OSAtomicTestAndClearBarrier(uint32_t __n, volatile void * __theAddress):这组函数用于测试__theAddress指向的值中由__n指定的bit位,如果该位未被清除,则清除它。需要注意的是最低bit位应该是1,而不是0。对于一个64-bit的值来说,如果要清除最高位的值,则__n应该是64。
  3. OSAtomicTestAndSet/OSAtomicTestAndSetBarrier(uint32_t __n, volatile void * __theAddress):与OSAtomicTestAndClear相反,这组函数测试值后,如果指定位没有设置,则设置它。

示例代码:

void * sharedBuffer(void)
{
    static void * buffer;
    if (buffer == NULL) {
        void * newBuffer = calloc(1, 1024);
        if (!OSAtomicCompareAndSwapPtrBarrier(NULL, newBuffer, &buffer)) {
            free(newBuffer);
        }
    }
    return buffer;
}

上述代码的作用是如果没有缓冲区,我们将创建一个newBuffer,然后将其写到buffer中。

布尔操作(AND, OR, XOR)

这组函数可根据以下两个规则来分类:

  1. 是否使用Barrier
  2. 返回值是原始值还是操作完成后的值

以And为例,有4个函数:OSAtomicAnd32OSAtomicAnd32BarrierOSAtomicAnd32OrigOSAtomicAnd32OrigBarrier。每个函数均带有两个参数:__theMask(uint32_t)__theValue(volatile uint32_t *)。函数将__theMask__theValue指向的值做AND操作。

类似,还有OR操作和XOR操作。

数学操作

这组函数主要包括:

  1. 加操作:OSAtomicAdd**OSAtomicAdd**Barrier
  2. 递减操作:OSAtomicDecrement**OSAtomicDecrement**Barrier
  3. 递增操作:OSAtomicIncrement**OSAtomicIncrement**Barrier

示例代码:


// 代码摘自ReactiveCocoa:RACDynamicSequence
	
- (void)dealloc {
	static volatile int32_t directDeallocCount = 0;
	
	if (OSAtomicIncrement32(&directDeallocCount) >= DEALLOC_OVERFLOW_GUARD) {
		OSAtomicAdd32(-DEALLOC_OVERFLOW_GUARD, &directDeallocCount);
	
		// Put this sequence's tail onto the autorelease pool so we stop
		// recursing.
		__autoreleasing RACSequence *tail __attribute__((unused)) = _tail;
	}
	
	_tail = nil;
}

小结

相较于@synchronized,OSAtomic原子操作更趋于数据的底层,从更深层次来对单例进行保护。同时,它没有阻断其它线程对函数的访问。

参考

  1. OSAtomic.h User-Space Reference
  2. Memory barrier
  3. Objc的底层并发API
  4. OSATOMIC与synchronized加锁的对比

人已赞赏
iOS文章

Masony 循环添加控件

2019-10-29 11:34:56

iOS文章

iOS-NSString和NSMutableString用法大全

2019-10-29 11:41:19

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索