iOS/MacOS为多线程、共享内存(变量)提供了多种的同步解决方案(即同步锁),对于这些方案的比较,大都讨论了锁的用法以及锁操作的开销,然后就开销表现排个序。春哥以为,最优方案的选用还是看应用场景,高频接口PK低频接口、有限冲突PK激烈竞争、代码片段耗时的长短,以上都是正确选用的重要依据,不同方案在其适用范围表现各有不同。这些方案当中,除了熟悉的iOS/MacOS系统自有的同步锁,另外还有两个自研的读写锁,还有应用开发中常见的set/get访问接口的原子操作属性。

1、@synchronized{}

Objective-C同步语法能够实现对block内的代码片段加锁, 可以指定任意一个Objective-C对象(id指针)作为锁“标记”,该语法将“标记”理解为token;

2、NSLock、NSRecursiveLock:

典型的面向对象的锁,即同步锁类,遵循Objective-C的NSLocking协议接口,前者支持tryLock,后者支持递归(可重入);

3、NSCondition、NSConditionLock:

基于信号量方式实现的锁对象,前者提供单独的信号量管理接口,相比后者用法上可以更为灵活,而后者在接口上更为直接、实用;

iOS/MacOS并没有提供读写锁,春哥尝试自己搞,Objective-C版的读写锁(ANLock),遵循读写锁特性,前者写锁耗时较小,后者支持递归;

5、pthread_mutex:

POSIX标准的unix多线程库(pthread)中使用的互斥量,支持递归,需要特别说明的是信号机制pthread_cond_wait同步方式也是依赖于该互斥量,pthread_cond_wait本身并不具备同步能力;

6、dispatch_semaphore:

GCD用于控制多线程并发的信号量,允许通过wait/signal的信号事件控制并发执行的最大线程数,当最大线程数降级为1的时候则可当作同步锁使用,注意该信号量并不支持递归;

7、OSSpinLock:

iOS/MacOS自有的自旋锁,其特点是线程等待取锁时不进内核,线程因此不挂起,直接保持空转,这使得它的锁操作开销降得很低,OSSpinLock是不支持递归的;

8、atomic(property) set/get:

利用set/get接口的属性实现原子操作,进而确保“被共享”的变量在多线程中读写安全,这已经是能满足部分多线程同步要求;

基础表现-锁操作耗时:

上图是常规的锁操作性能测试(iOS7.0SDK,iPhone6模拟器,Yosemite 10.10.5),垂直方向表示耗时,单位是秒,总耗时越小越好,水平方向表示不同类型锁的锁操作,具体又分为两部分,左边的常规lock操作(比如NSLock)或者读read操作(比如ANReadWriteLock),右边则是写write操作,图上仅有ANReadWriteLock和ANRecursiveRWLock支持,其它不支持的则默认为0,图上看出,单从性能表现,原子操作是表现最佳的(0.057412秒),@synchronized则是最耗时的(1.753565秒) (测试代码)

正如前文所述,不同方案各有侧重,适用于不用的场景,不能唯性能论高低:

原子操作虽然性能很好,但仅限于set/get,比如对列表的插入移除操作需要做同步则无能为力,支持不到,所以适用于一些实例成员变量的读写同步;

得益于不进内核不挂起的方式,OSSpinLock有着优异的性能表现,然而在高并发执行(冲突概率大,竞争激烈)的时候,又或者代码片段比较耗时(比如涉及内核执行文件io、socket、thread等),就容易引发CPU占有率暴涨的风险,因此更适用于一些简短低耗时的代码片段;

上图为OSSpinLock等待取锁时的耗时测试用例代码,下图为测试结果,图中可以看到,等待取锁时,如果异步线程比较耗时,CPU占有率会有一个飙升 (测试代码)

dispatch_semaphore的性能表现出乎意料之外的好,也没有OSSpinLock的CPU占有率暴涨的问题,然而原本是用于GCD的多线程并发控制,也是信号量机制,是否适用于常规同步锁有待实践验证,春哥这里仅提供选择,不做推荐;

上图为dispatch_semaphore测试用例

pthread_mutex是pthread经典的基于互斥量机制的同步锁,特性、性能以及稳定各方面都已被大量项目所验证,也是春哥比较推荐作为常规同步锁首选;

上图为pthread_mutex用法举例

读写锁的在锁操作耗时上明显不占优势,读写锁的主要性能优势在于多线程高并发量的场景,这时候锁竞争可能会非常激烈,使用一般的锁这时候并发性能都会明显下降,读写锁对于所有读操作能够把同步放开,进而保持并发性能不受影响;以pthread_mutex和ANRecursiveRWLock为例,假设mutex的lock耗时为lk,则rw的read lock耗时为2.7lk(从性能测试图表数据得出),read操作耗时为rd,1000次的多线程接口访问:

mutex总耗时 = 1000*lk + 1000*rd

rw总耗时 = 1000*2.7*lk + 1000/c*rd

其中c表示应用的并发数,根据开发文档和技术资料,iOS第二条线程起stack为512KB,而单个应用useable memory size在50MB以内,即c

假设线程数取中值c=50(严格来说,线程数不等于冲突计数,冲突计数很可能会比线程数小得多,线程同步运行不代表就即刻会发生冲突),当 mutex总耗时 > rw总耗时:

mutex总耗时 > rw总耗时 =》 50*lk + 50*rd > 50*2.7lk + rd =》 49*rd > 85*lk =》 rd > 1.73*lk

可以看出,只要read操作耗时超过锁操作耗时的1.7倍(这其实很容易达到的),读写锁的性能就会占优势

假设线程数c=2(如上述,这里是假设了两个线程之间是竞争了,发生冲突,实际未必):

mutex总耗时 > rw总耗时 =》 2*lk + 2*rd > 5.4*lk + rd =》 rd > 3.4lk

即使只有两个并发线程,只要read操作耗时超过锁操作耗时的3.4倍,读写锁的性能还会占优势

假设线程数c=1:

mutex总耗时 > rw总耗时 =》0 > 1.7lk

这显然不成立,说明当单个线程的时候,rw的性能不可能有优势。这也好理解,这时候的mutex和rw的读操作都相当完全同步,不论是mutex还是rw,性能完全取决于锁操作本身,而rw在锁操作耗时上就不占优势,所以mutex总耗时总是要小于rw总耗时的。

上图是mutex锁和rw锁read操作的耗时测试用例,下图为测试结果,read操作设置为100微秒,mutex锁的总耗时是rw锁的5倍多,read操作的耗时远比锁操作大许多(2k倍),根据上述恒等式计算可以得出实际的冲突计数c=5 (测试代码)

其它方案的讨论:

a、NSCondition和NSConditionLock实际使用的性能表现并任何优势,然而条件锁的意义在于对信号量做了面向对象封装;

b、NSLock和NSRecursiveLock在性能表现上与mutex算比较接近,用法上也并无二致,因此,常规情况,NSRecursiveLock和mutex之间的选择,春哥以为更多是习惯和偏好的问题;

c、@synchronized似乎是这些方案当中性能表现最不佳的,那是不是应该完全抛弃呢?春哥倒不这么认为,@synchronized最大的特点在于“快捷”,同步语法仅仅需要一个对象(id指针)作为互斥量,而且还不限于实例对象,类对象也能够支持,这就使得类方法中做同步变得简单不少,block用法也使得代码更紧凑,内存管理更稳健,非常适合一些低频而又不得不同步的逻辑,比如单例初始化、启动加载等等。

综合上述分析与讨论,总结有以下几点原则:

1、总的来看,推荐pthread_mutex作为实际项目的首选方案;

2、对于耗时较大又易冲突的读操作,可以使用读写锁代替pthread_mutex;

3、如果确认仅有set/get的访问操作,可以选用原子操作属性;

4、对于性能要求苛刻,可以考虑使用OSSpinLock,需要确保加锁片段的耗时足够小;

5、条件锁基本上使用面向对象的NSCondition和NSConditionLock即可;

6、@synchronized则适用于低频场景如初始化或者紧急修复使用;

起底多线程同步锁(iOS)的更多相关文章

  1. 第十五章、Python多线程同步锁,死锁和递归锁

    目录 第十五章.Python多线程同步锁,死锁和递归锁 1. 引子: 2.同步锁 3.死锁 引子: 4.递归锁RLock 原理: 不多说,放代码 总结: 5. 大总结 第十五章.Python多线程同步 ...

  2. Java多线程同步锁的理解

    java主要通过synchronized的关键字来实现的.让我们从一个买票程序说起吧. package com.day04; /** * * @author Administrator 问题描述:使用 ...

  3. 多线程同步锁和死锁以及synchronized与static synchronized 的区别

    线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程.一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序.简而言之:一个程序运行后至少有一个进程,一个进程 ...

  4. iOS多线程同步锁

    在iOS中有几种方法来解决多线程访问同一个内存地址的互斥同步问题: 方法一,@synchronized(id anObject),(最简单的方法)会自动对参数对象加锁,保证临界区内的代码线程安全 @s ...

  5. c#中多线程同步Lock(锁)的研究以及跨线程UI的操作

    本文只针对C#中,多线程同步所用到的锁(lock)作为研究对象.由于想更直观的显示结果,所以,在做demo的时候,就把多线程通过事件操作UI的代码也写了出来,留作备忘和分享吧. 其实多线程的同步,使用 ...

  6. c#中多线程同步Lock(锁)的研究以及跨线程UI的操作 (转)

    https://www.cnblogs.com/tommyheng/p/4104552.html 本文只针对C#中,多线程同步所用到的锁(lock)作为研究对象.由于想更直观的显示结果,所以,在做de ...

  7. 0037 Java学习笔记-多线程-同步代码块、同步方法、同步锁

    什么是同步 在上一篇0036 Java学习笔记-多线程-创建线程的三种方式示例代码中,实现Runnable创建多条线程,输出中的结果中会有错误,比如一张票卖了两次,有的票没卖的情况,因为线程对象被多条 ...

  8. Java多线程---同步与锁

    一,线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏. 二.同步和锁定 1.锁的原理 Java中每个对象都有一个内置锁. 当程序运行到非静态的synchronized同步方法上时,自动 ...

  9. 【Java】多线程冲突解决——同步锁

       转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/5827547.html    解决并行冲突最有效的方法就是加同步锁,主要有以下几种方法:   1:动态方法 ...

随机推荐

  1. java 读取文件中文乱码问题

    很少写java io的代码,今天整了一个发现 本地调试好好的,放到jmeter里就打印乱码.一番折腾,终于搞定~直接上代码: List<Order> orderList = new Arr ...

  2. 李洪强iOS开发本人集成环信的经验总结_09_处理好友请求

    李洪强iOS开发本人集成环信的经验总结_09_处理好友请求 实现这种效果: 01 - 遵守处理好友请求的代理协议 02  - 设置代理 03 - 实现代理方法 04 - 实现代理中用到的方法 

  3. 李洪强iOS开发本人集成环信的经验总结_03_注册

    李洪强iOS开发本人集成环信的经验总结_03_注册 环信一共提供了三种注册的方法:  01 同步注册:  02 异步注册:  03 - 使用代理回调进行注册,但是3.0没有了,3.0之前有  调用注册 ...

  4. ajax readyState的五种状态详解

    通过ajax的readyState的值,我们可以知道当前的这个http请求处于什么状态.对于web的调试是比较重要的. readyState 状态说明: (0)未初始化 此阶段确认XMLHttpReq ...

  5. nginx 详解

    #运行用户 #user  nobody;   #启动进程,通常设置成和cpu的数量相等或者2倍于cpu的个数(具体结合cpu和内存).默认为1 worker_processes  1;   #全局的错 ...

  6. NSString的常用方法

    创建一个新字符串并将其设置为 path 指定的文件的内容,使用字符编码enc,在error上返回错误 + (id)stringWithContentsOfURL:(NSURL *)url encodi ...

  7. java中synchronized的用法详解

    记下来,很重要. Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 一.当两个并发线程访问同一个对象object中的这个synchron ...

  8. 【 D3.js 高级系列 — 1.0 】 文本的换行

    在 SVG 中添加文本是使用 text 元素.但是,这个元素不能够自动换行,超出的部分就显示不出来了,怎么办呢? 高级系列开篇前言 从今天开始写高级系列教程.还是那句话,由于本人实力有限,不一定保证入 ...

  9. 【转】C,C++中使用可变参数

    可变参数即表示参数个数可以变化,可多可少,也表示参数的类型也可以变化,可以是 int,double还可以是char*,类,结构体等等.可变参数是实现printf(),sprintf()等函数的关键之处 ...

  10. 基于MongoDB分布式存储进行MapReduce并行查询

    中介绍了如何基于Mongodb进行关系型数据的分布式存储,有了存储就会牵扯到查询.虽然用普通的方式也可以进行查询,但今天要介绍的是如何使用MONGODB中提供的MapReduce功能进行查询.     ...