iOS常用锁的研究

iOS常用锁的研究

背景

iOS并发编程除了常用的多线程技术外,线程间同步的方法也是另外一个重要的点. 公司的项目时间跨度比较长,多线程的实现方式从早期的NSThread到现在GCD和NSOperation+NSOperationQueue的方式都有,加锁方式也是多种多样.之前并不清楚各种锁的使用场景和效率,这里做了简单的研究把总结的一点心得记录下来.

OSSpinLock

OSSpinLock为什么第一个提起呢?因为其效率是最高的.本人是较晚才接触到这个锁的,最早是从facebook的KVOController里面看到的(不过现在已经用pthread_mutex重写,原因下面会提到).
自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名.
因为少了状态切换,所以效率也会高一些,但是如果执行的代码本身比较耗时且调用的频次也比较频繁的话,那么CPU的压力会陡增且效率也会大打折扣.
所以一般的锁住部分的代码段执行的操作比较轻量级那么OSSpinLock还是非常高效的,比如访问一些可变集合类型的变量.
下面是维基百科关于SpinLock的说明,同样适用于OSSpinLock.
后来偶然读到一篇博客<不再安全的 OSSpinLock>,iOS中使用OSSpinLock已经不再安全了,包括Apple自己也对代码中的OSSpinLock做了替换,Google protobuf的objective-c的源码中对OSSpinLock也用dispatch_semaphore进行了替换,另外一种替换方式是采用pthread_mutex如上面提到的KVOController.
示例代码:

[OSSpinLock lock = OS_SPINLOCK_INIT;
OSSpinLockLock(&lock);
// do something
OSSpinLockUnlock(&lock);

pthread_mutex

互斥锁,这个是C语言形式的加锁方式,最早是在<Unix高级编程>里面接触到的.
互斥锁的使用过程中,主要有pthread_mutex_init, pthread_mutex_destory, pthread_mutex_lock, pthread_mutex_unlock这几个函数以完成锁的初始化,锁的销毁,上锁和释放锁操作.
示例代码:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&mutex);
// do something
pthread_mutex_unlock(&mutex);

小结1

Mutex和SpinLock的区别(以下是网上一篇博客的摘录,个人认为解释的比较好):
实现原理上来讲,Mutex属于sleep-waiting类型的锁。例如在一个双核的机器上有两个线程(线程A和线程B),它们分别运行在Core0和 Core1上。假设线程A想要通过pthread_mutex_lock操作去得到一个临界区的锁,而此时这个锁正被线程B所持有,那么线程A就会被阻塞 (blocking),Core0 会在此时进行上下文切换(Context Switch)将线程A置于等待队列中,此时Core0就可以运行其他的任务(例如另一个线程C)而不必进行忙等待。而Spin lock则不然,它属于busy-waiting类型的锁,如果线程A是使用pthread_spin_lock操作去请求锁,那么线程A就会一直在 Core0上进行忙等待并不停的进行锁请求,直到得到这个锁为止。

自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是 否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。其作用是为了解决某项资源的互斥使用。因为自旋锁不会引起调用者睡眠,所以自旋锁的效率远 高于互斥锁。虽然它的效率比互斥锁高,但是它也有些不足之处:
1、自旋锁一直占用CPU,他在未获得锁的情况下,一直运行--自旋,所以占用着CPU,如果不能在很短的时 间内获得锁,这无疑会使CPU效率降低。
2、在用自旋锁时有可能造成死锁,当递归调用时有可能造成死锁,调用有些其他函数也可能造成死锁,如 copy_to_user()、copy_from_user()、kmalloc()等。

dispatch_semaphore

信号量是基于计数器的一种线程同步机制.
一般的如果只允许一个线程执行代码块的话,那么可以使用如下方式创建,信号量的计数为1.
示例代码:

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

dispatch_semaphore支持两个操作:等待和通知.
等待(dispatch_semaphore_wait): 让信号量减1,如果信号量小于0时则会一直等待,直到接收到另一个信号量通知.
通知(dispatch_semaphore_signal): 让信号量加1, 如果之前的信号量小于0,此方法会唤起正在wait的线程,然后return.
以上是从头文件摘录的两段注释简单翻译过来的,原注释应该描述的更为清楚.
以下是通过wait和signal方式来加锁
示例代码:

dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);
// do something
dispatch_semaphore_signal(semaphore);

Objective-C中的锁

Objective-C有一些常用的锁, 他们的接口实际上都是通过NSLocking协议定义的,它定义了lock和unlock方法.你使用这些方法来获取和释放该锁.
常用的锁有:NSLock,NSRecursiveLock,NSCondition,NSConditionLock.
以NSLock为例:

NSLock *lock = [[NSLock alloc] init];
[lock lock];
// do something
[lock unlock];

上面苹果的文档已经有比较详细的描述了.

使用GCD进行线程同步

除了上面提到的dispatch_semaphore以外,dispatch_queue也是线程同步的一种手段.
通常的我们可以使用串行队列来进行线程同步,比较典型的使用就是FMDB中FMDatabaseQueue的实现.
示例代码:

dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
    // do something
});

Dispatch Barrier API,也提供线程同步的一种手段. 此类API更适合实现读写锁的机制.
示例代码:

dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);

dispatch_sync(queue, ^{
    // read;
});
    
dispatch_barrier_async(queue, ^{
    // write;
});

原子操作

iOS平台下的原子操作函数都以OSAtomic开头的,在<libkern/OSAtomic.h>里面.不同线程如果通过原子操作函数对同一变量进行操作,可以保证一个线程的操作不会影响到其他线程内对此变量的操作,因为这些操作都是原子式的.因为原子操作只能对内置类型进行操作,所以原子操作能够同步的线程只能位于同一个进程的地址空间内.
原子操作快速高效,常用的加/减/自增/自减等都有对应的API实现.
主要可以应用到一些计数的同步中.

@synchronized关键字

为什么最后说它,因为这个关键字是最方便使用,也是最经常被误用的一种加锁方式. 因为@synchronized默认添加异常处理机制,所以效率上面会有所牺牲. 这里有篇博客<More than you want to know about @synchronized>对@synchronized做了比较深入的阐述.
通常的如果不需要异常处理机制,不建议使用@synchronized,它的效率确实比较低.

小结2

至此对于iOS中常用的一些线程同步手段就介绍完了.
可能有些人会问我,我会常用哪些锁? 个人比较偏向使用 dispatch_queue 串行队列, 因为它可以简单的把代码包起来, 以免漏写了unlock. 其他的早期偶尔会使用OSSpinLock(后来发现其缺陷后改用dispatch_semaphore). @synchronized会在遗留代码中使用, 为了保持一致.
以下是读到的一些开源代码中一些锁的实现.

facebook AsyncDisplayKit 中 ASThread 的实现;
CocoaAsyncSocket 中 GCDAsyncSocket 的实现;
AFNetworking 中 AFURLSessionManager 的实现;

附上自己关于上述常用锁执行效率的统计Demo,github仓库中是一个workspace,本文中的相关内容在Demo_Locks工程中.

对1,000,000次自增操作的加锁结果统计,正序排序
2016-09-25 02:48:59.593688 Demo_Locks[4110:655534] lock : NoLock, time : 2502520 ns, 0.002503 s
2016-09-25 02:48:59.593905 Demo_Locks[4110:655534] lock : SpinLock, time : 10154576 ns, 0.010155 s
2016-09-25 02:48:59.593933 Demo_Locks[4110:655534] lock : Atomic, time : 11869955 ns, 0.011870 s
2016-09-25 02:48:59.593958 Demo_Locks[4110:655534] lock : Semaphore, time : 14753828 ns, 0.014754 s
2016-09-25 02:48:59.593981 Demo_Locks[4110:655534] lock : Mutex, time : 25518736 ns, 0.025519 s
2016-09-25 02:48:59.594002 Demo_Locks[4110:655534] lock : GCD, time : 35667374 ns, 0.035667 s
2016-09-25 02:48:59.594024 Demo_Locks[4110:655534] lock : NSCondition, time : 38879077 ns, 0.038879 s
2016-09-25 02:48:59.594046 Demo_Locks[4110:655534] lock : RWLock, time : 47595821 ns, 0.047596 s
2016-09-25 02:48:59.594068 Demo_Locks[4110:655534] lock : NSLock, time : 54020795 ns, 0.054021 s
2016-09-25 02:48:59.594089 Demo_Locks[4110:655534] lock : NSRecursiveLock, time : 61731904 ns, 0.061732 s
2016-09-25 02:48:59.594112 Demo_Locks[4110:655534] lock : Synchronized, time : 109095555 ns, 0.109096 s
2016-09-25 02:48:59.594168 Demo_Locks[4110:655534] lock : NSConditionLock, time : 128421367 ns, 0.128421 s

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

推荐阅读更多精彩内容