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. 【mysql的设计与优化专题(3)】字段类型与合理的选择字段类型

    本篇博客稍微有点长,它实际上包括两个内容:一是mysql字段类型的介绍,二是在mysql建表过程中是如何正确选择这些字段类型; 字段类型 数值 MySQL 的数值数据类型可以大致划分为两个类别,一个是 ...

  2. Ubuntu 12.04安装字体

    http://www.2cto.com/os/201210/160645.html 安装方法,终端输入:  $ sudo thunar /usr/share/fonts/truetype  待ture ...

  3. spring的组成

    ① Spring Core:核心容器,BeanFactory提供了组件生命周期的管理,组件的创建,装配,销毁等功能 SpringContext:ApplicationContext,扩展核心容器,提供 ...

  4. jquery的ajax向后台servlet传递json类型的多维数组

    后台运行结果:                                                                                      前台运行结果: ...

  5. p2p穿透技术

    ios 怎么和wifi外设摄像头实时传输视频 ios 控制wifi摄像头外设的拍照.录像.删除照片等等都可以通过tcp/ip 发送定义好的json指令实现. 但是不知道怎么和wifi外设摄像头实时传输 ...

  6. Hibernate映射之实体映射<转载>

    实体类与数据库之间存在某种映射关系,Hibernate依据这种映射关系完成数据的存取,因此映射关系的配置在Hibernate中是最关键的.Hibernate支持xml配置文件与@注解配置两种方式.xm ...

  7. [原]Unity3D深入浅出 - Shader基础开发

    概述 简单来讲,shader是为渲染管线中的特定处理截断提供算法的一段代码.Shader是伴随着可编程渲染管线出现的,开发者可使用Shader对渲染过程加以控制,拥有更大的创作控件,因此Shader的 ...

  8. [原]Unity3D深入浅出 - 认识开发环境中的自带的Package资源包

    Character Controller:角色控制器 Glass Refraction(pro only):玻璃反射资源包 Image Effects :图像效果资源包 Light Cookies:光 ...

  9. [swustoj 856] Huge Tree

    Huge Tree(0856) 问题描述 There are N trees in a forest. At first, each tree contains only one node as it ...

  10. LeetCode Maximum Depth of Binary Tree (求树的深度)

    题意:给一棵二叉树,求其深度. 思路:递归比较简洁,先求左子树深度,再求右子树深度,比较其结果,返回:max_one+1. /** * Definition for a binary tree nod ...