OSAtomic原子操作

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= [superinit];

if(self==nil)returnnil;

_disposeBlock = (void*)CFBridgingRetain([blockcopy]);

    OSMemoryBarrier();

returnself;

}

自旋锁(Spinlocks)

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

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

示例代码:

// 代码来自ReactiveCocoa:RACCompoundDisposable类

- (void)dispose {

#ifRACCompoundDisposableInlineCount

    RACDisposable *inlineCopy[RACCompoundDisposableInlineCount];

#endif

CFArrayRefremainingDisposables =NULL;

    OSSpinLockLock(&_spinLock);

    {

_disposed =YES;

#ifRACCompoundDisposableInlineCount

for(unsignedi =0; i < RACCompoundDisposableInlineCount; i++) {

            inlineCopy[i] = _inlineDisposables[i];

_inlineDisposables[i] =nil;

        }

#endif

        remainingDisposables = _disposables;

_disposables =NULL;

    }

    OSSpinLockUnlock(&_spinLock);

#ifRACCompoundDisposableInlineCount

// Dispose outside of the lock in case the compound disposable is used

// recursively.

for(unsignedi =0; i < RACCompoundDisposableInlineCount; i++) {

        [inlineCopy[i] dispose];

    }

#endif

if(remainingDisposables ==NULL)return;

CFIndexcount =CFArrayGetCount(remainingDisposables);

CFArrayApplyFunction(remainingDisposables,CFRangeMake(0, count), &disposeEach,NULL);

CFRelease(remainingDisposables);

}

原子队列操作

队列操作主要包含两类:

不加锁的FIFO入队和出队原子操作,包含OSAtomicFifoDequeue和OSAtomicFifoEnqueue两个函数

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

比较和交换

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

OSAtomicCompareAndSwap**[Barrier](type __oldValue, type __newValue, volatile type *__theValue):这组函数用于比较__oldValue是否与__theValue指针指向的内存位置的值匹配,如果匹配,则将__newValue的值存储到__theValue指向的内存位置。可以根据需要使用barrier版本。

OSAtomicTestAndClear/OSAtomicTestAndClearBarrier( uint32_t __n, volatile void *__theAddress ):这组函数用于测试__theAddress指向的值中由__n指定的bit位,如果该位未被清除,则清除它。需要注意的是最低bit位应该是1,而不是0。对于一个64-bit的值来说,如果要清除最高位的值,则__n应该是64。

OSAtomicTestAndSet/OSAtomicTestAndSetBarrier(uint32_t __n, volatile void *__theAddress):与OSAtomicTestAndClear相反,这组函数测试值后,如果指定位没有设置,则设置它。

示例代码:

void*sharedBuffer(void)

{

staticvoid* buffer;

if(buffer ==NULL) {

void* newBuffer =calloc(1,1024);

if(!OSAtomicCompareAndSwapPtrBarrier(NULL, newBuffer, &buffer)) {

free(newBuffer);

        }

    }

returnbuffer;

}

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

布尔操作(AND, OR, XOR)

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

是否使用Barrier

返回值是原始值还是操作完成后的值

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

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

数学操作

这组函数主要包括:

加操作:OSAtomicAdd**, OSAtomicAdd**Barrier

递减操作:OSAtomicDecrement**, OSAtomicDecrement**Barrier

递增操作:OSAtomicIncrement**, OSAtomicIncrement**Barrier

示例代码:

// 代码摘自ReactiveCocoa:RACDynamicSequence

- (void)dealloc {

staticvolatileint32_tdirectDeallocCount =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原子操作更趋于数据的底层,从更深层次来对单例进行保护。同时,它没有阻断其它线程对函数的访问。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容