iOS开发中自旋和互斥锁的理解以及所有锁的性能比较

补充:

可以看到除了 OSSpinLock 外,dispatch_semaphore  pthread_mutex 性能是最高的。苹果在新系统中已经优化了 pthread_mutex 的性能,所以它看上去和 OSSpinLock 差距并没有那么大了。

可以看到YYKit组件中YYCache 和 YYImageCoder大量使用 dispatch_semaphore  pthread_mutex这两个锁

OSSpinLock 自旋锁(虽然已经被证明不安全 优先级翻转),性能最高的锁。原理很简单,就是一直 do while 忙等。它的缺点是当等待时会消耗大量 CPU 资源,所以它不适用于较长时间的任务。对于内存缓存的存取来说,它非常合适。

dispatch_semaphore 是信号量,但当信号总量设为 1 时也可以当作锁来。在没有等待情况出现时,它的性能比 pthread_mutex 还要高,但一旦有等待情况出现时,性能就会下降许多。相对于 OSSpinLock 来说,它的优势在于等待时不会消耗 CPU 资源。对磁盘缓存来说,它比较合适

不存在等待的情况,例如不涉及到磁盘这种文件读写,dispatch_semaphore性能很高,如果涉及到的任务等待时间较长,就需要用pthread_mutex(OSSpinLock不安全就可以先不用了)

按计算机网络书上的介绍,mutex是信号量的简化版本,可以简单判定,pthread_mutex可能是用信号量来实现的,内部也会用在进入临界区之前根据条件进行休眠。这就可以避免优先级翻转的问题,在获取锁失败的时候,调用thread_yield将CPU主动放弃给另一个线程,这样就没有忙等待,下一次时间片轮转到的时候再进行锁的测试,YY那里有个测试表,信号量性能略微高于pthread_mutex,可能是因为pthread_mutex类型转换的问题,功能更丰富,还有递归锁,上层封装的转换可能就性能稍微差一点。

 

为什么有锁这个概念(操作系统小知识,可以忽略)?

这是操作系统方面的基础知识,还是记录下,引出锁的概念

进程

  • 进程:资源分配的最小单位

  • 定义:一个进程就是一个正在执行程序的实例;进程是某种类型的活动,它有程序、输入、输出以及状态

  • 严格说,某个瞬间,CPU只能运行一个进程,一个程序运行了两次,那么就算运行了两次进程,进程可以拥有多个线程

  • 守护进程(daemon):运行在后台的进程

进程的状态:

  • 运行态(此进程实际占用CPU)

  • 就绪态(可运行,但因其他进程正在运行而暂时停止)

  • 阻塞态(除非某种外部事件发生,否则进程不能运行)

每个进程占用一个进程表项,该表项包含了进程状态的重要信息,包括程序计数器、堆栈指针、内存分配状况、所打开文件的状态、帐号和调度信息

 

线程

  • 线程:“轻量级的进程”(lightweight process)

  • 为什么多线程?

    • 并行实体共享同一个地址空间和所有可用数据的能力

    • 线程比进程更轻量级,所以它们比进程更容易、更快创建,也更容易撤销

    • 拥有多个线程允许这样活动彼此重叠进行,从而会加快应用程序执行速度

  • 线程模型

    • 多个线程共享同一个地址空间和其他资源,比如共享全局变量

    • 进程中的不同线程不像不同进程之间那样存在很大的独立性

    • 严格来说,同一时刻只有一个线程占用CPU,但高速切换给人带来并行运行的假象

    • 线程与进程一样,也具有三种状态,运行态、就绪态、阻塞态,并且转化关系也一样(见上文的图)

    • 每个线程都有其自己的堆栈,其中有一帧,供各个被调用但还没返回的过程中使用,在该帧存放了相应过程的局部变量以及过程调用完成之后使用的返回地址

    • 常见的线程调用:thread_yield,它允许线程自动放弃CPU从而让另一个线程运行,因为不同于进程,线程无法利用时钟中断强制让出CPU

线程和进程的区别:

  • 调度 :在引入线程的操作系统中,线程是调度和分配的基本单位 ,进程是资源拥有的基本单位 。把传统进程的两个属性分开,线程便能轻装运行,从而可显著地提高系统的并发程度。 在同一进程中,线程的切换不会引起进程的切换;在由一个进程中的线程切换到另一个进程中的线程时,才会引起进程的切换。

  • 并发性 :在引入线程的操作系统中,不仅进程之间可以并发执行,而且在一个进程中的多个线程之间亦可并发执行,因而使操作系统具有更好的并发性,从而能更有效地使用系统资源和提高系统吞吐量。

  • 拥有资源 :不论是传统的操作系统,还是设有线程的操作系统,进程都是拥有资源的一个独立 单位,它可以拥有自己的资源。 一般地说,线程自己不拥有系统资源(只有一些必不可少的资源),但它可以访问其隶属进程的资源。

  • 系统开销: 由于在创建或撤消进程时,系统都要为之分配或回收资源,因此,操作系统所付出的开销将显著地大于在创建或撤消线程时的开销。 进程切换的开销也远大于线程切换的开销。

 

线程有内核级别的和用户级别的:

内核级别:

全系统可以竞争,由内核操控,多核直接可以运行多个线程,可以理解为内核也是一个超级进程,这个进程里面的线程最屌,优先级最高,可以全系统参与资源竞争

有多核的时候,内核线程可以搭载到不同的cpu上面实现真正的并行

用户级别:

存在于用户空间,内核看不到这个,也不会管这个线程,资源寄宿在对应的进程里面,由程序调度,资源竞争就是在进程拿到的资源为整体进行竞争

内核不参与,控制简单,易于扩展,但是资源调度是寄宿在进程下的,所以当进程状态为运行时,获得时间片,然后分给该进程下的多线程,这里可不像内核级别的多核搭载并行了,这里看上去是一起执行的,其实是根据调度算法例如时间片轮转,分时复用,每个线程拿到一小段时间片,切换运行的,这就是并发

 

进程间通信

  • 竞争条件(race condition):两个或多个进程读写某些共享数据,而最后结果取决于进程运行时的精确时序

  • 互斥(mutual exclusion):解决竞争条件的手段,确保当一个进程在使用一个共享变量或文件时,其他进程不能做同样的操作

  • 临界区域(critical region):对共享内存进行访问的程序片段称作临界区域,或临界区(critical section)

资源的竞争,就出现了锁(自旋和互斥两大类)

 

当一个进程在临界区中更新共享内存时,其他进程不会进入临界区,不会带来麻烦

忙等(进入临界区检查是否可以进入,不可以则原地等待,直到允许为止自旋锁OSSpinLock)

1.屏蔽终端

当程序进入临界区的时候立刻屏蔽所有中断。通俗的理解如下,cpu在时钟中断的情况下才会发生进程切换,如果某个进程进入临界区,屏蔽了时钟中断带来的时间片,就不会有其他线程来执行临界区的代码。但是屏蔽终端是操作系统层面的,用户最好不要干涉

2.严格轮换法

连续测试一个变量直到某个值出现为止,do while 忙等。用于忙等的锁称为自旋锁

忙等问题:优先级翻转,H高优先级,L低优先级,H处于就绪态的时候他就可以运行,但是当某一时刻L进入临界区,此时H是就绪态,现在H开始忙等,由于H就绪,L不会被调用,因此L无法离开临界区,H会无脑等下去

 

信号量:睡眠和唤醒

信号量1的时候可以用来做锁互斥用,互斥锁pthread_mutex和信号量 dispatch_semaphore

按计算机网络书上的介绍,mutex是信号量的简化版本,可以简单判定,pthread_mutex可能是用信号量来实现的,内部也会用在进入临界区之前根据条件进行休眠。这就可以避免优先级翻转的问题,在获取锁失败的时候,调用thread_yield将CPU主动放弃给另一个线程,这样就没有忙等待,下一次时间片轮转到的时候再进行锁的测试,YY那里有个测试表,信号量性能略微高于pthread_mutex,可能是因为pthread_mutex类型转换的问题,功能更丰富,还有递归锁,上层封装的转换可能就性能稍微差一点

 

前言

写这个主要是整合下看到的资料,某天突然看到一到腾讯的面试题,里面就有一提就是让我们谈谈我们自己所认识的iOS中的锁,自己平时看到的就在RAC中的OSSPinLock,AF一些大型框架里面的disaptch_semaphore,还有就是NSLock和@synchronize这几个常见的锁了,用法是很简单,但是别人让你谈谈你自己的理解,因此就有了下面的资料,主要来自于一下几篇文章

深入理解伪代码介绍

YY大哥的不安全OSSPinLock

简书小哥的用法介绍

非常感谢这些大神的资料,对这个知识点有了基本的了解

 

这里贴两个面试基础题目,对后续文章看起来会理解更好一点

QA1:自旋和互斥对比?

自旋锁和互斥锁 

  • 相同点:都能保证同一时间只有一个线程访问共享资源。都能保证线程安全。

  • 不同点: 

    • 互斥锁:如果共享数据已经有其他线程加锁了,线程会进入休眠状态等待锁。一旦被访问的资源被解锁,则等待资源的线程会被唤醒。

    • 自旋锁:如果共享数据已经有其他线程加锁了,线程会以死循环的方式等待锁,一旦被访问的资源被解锁,则等待资源的线程会立即执行。

  • 自旋锁的效率高于互斥锁。

使用自旋锁时要注意:

  • 由于自旋时不释放CPU,因而持有自旋锁的线程应该尽快释放自旋锁,否则等待该自旋锁的线程会一直在哪里自旋,这就会浪费CPU时间。

  • 持有自旋锁的线程在sleep之前应该释放自旋锁以便其他咸亨可以获得该自旋锁。内核编程中,如果持有自旋锁的代码sleep了就可能导致整个系统挂起。

    使用任何锁都需要消耗系统资源(内存资源和CPU时间),这种资源消耗可以分为两类:

        1.建立锁所需要的资源

        2.当线程被阻塞时所需要的资源

 

两种锁的加锁原理:

互斥锁:线程会从sleep(加锁)——>running(解锁),过程中有上下文的切换(主动出让时间片,线程休眠,等待下一次唤醒),cpu的抢占,信号的发送等开销。

自旋锁:线程一直是running(加锁——>解锁),死循环(忙等 do-while)检测锁的标志位,机制不复杂。

 

介绍

锁:在计算机科学中,锁是一种同步机制,用于在存在多线程的环境中实施对资源的访问限制。你可以理解成它用于排除并发的一种策略!你可以理解为为了防止多线程访问下资源的抢夺,保持线程同步的方式,以下就是常用的几个锁

1.@synchronized 关键字加锁 
2. NSLock 对象锁 
3. NSCondition  
4. NSConditionLock 条件锁 
5. NSRecursiveLock 递归锁 
6. pthread_mutex 互斥锁(C语言) 
7. dispatch_semaphore 信号量实现加锁(GCD) 
8. OSSpinLock  自旋锁 

9.pthread_rwlock

10.POSIX Conditions

11.os_unfair_lock  iOS10之后替代OSSPinLock的锁,解决了优先级反转的问题

 

这几个比较常见的基本用法可以看这里点击打开链接,这个这里就不介绍了,用法都很简单

 

1.OSSPinLock

 

OSSpinLock 不再安全,主要原因发生在低优先级线程拿到锁时,高优先级线程进入忙等(busy-wait)状态,消耗大量 CPU 时间,从而导致低优先级线程拿不到 CPU 时间,也就无法完成任务并释放锁。这种问题被称为优先级反转。

为什么忙等会导致低优先级线程拿不到时间片?这还得从操作系统的线程调度说起。

现代操作系统在管理普通线程时,通常采用时间片轮转算法(Round Robin,简称 RR)。每个线程会被分配一段时间片(quantum),通常在 10-100 毫秒左右。当线程用完属于自己的时间片以后,就会被操作系统挂起,放入等待队列中,直到下一次被分配时间片。

忙等这种自旋锁的实现原理

do {  
    Acquire Lock
        Critical section  // 临界区
    Release Lock
        Reminder section // 不需要锁保护的代码
}

在 Acquire Lock 这一步,我们申请加锁,目的是为了保护临界区(Critical Section) 中的代码不会被多个线程执行。

bool lock = false; // 一开始没有锁上,任何线程都可以申请锁  
do {  
    while(lock); // 如果 lock 为 true 就一直死循环,相当于申请锁
    lock = true; // 挂上锁,这样别的线程就无法获得锁
        Critical section  // 临界区
    lock = false; // 相当于释放锁,这样别的线程可以进入临界区
        Reminder section // 不需要锁保护的代码        
}

上面的伪代码就是实现自旋锁的基本原理,初始化一个lock的全局变量,一开始是false,while(lock)的意思是,当lock为true的时候,就进行忙等死循环(do-while申请锁),由于一开始是false,直接退出循环,然后lock锁上,执行临界区代码,也就是这个时候有其他线程访问,lock已经被锁上,while循环会一直忙等,处于申请锁状态,上一个锁执行完任务,就会解锁,这个时候lock变成了false,之前其他线程忙等状态下的条件变了,跳出循环,下一个线程执行lock=true,进门执行任务,其他线程继续等待。这里有个问题,如果一开始有多个线程同时执行 while 循环,他们都不会在这里卡住,而是继续执行,这样就无法保证锁的可靠性了。解决思路也很简单,只要确保申请锁的过程是原子操作即可。

 

用一个原子性操作 test_and_set 来完成,可以理解为线程进来的时候通过锁的上一个状态在判断一次,它用伪代码可以这样表示:

bool test_and_set (bool *target) {  
    bool rv = *target; 
    *target = TRUE; 
    return rv;
}

该段代码的意思也很清晰,通过传入开关地址,先用局部变量存储一开始开关的状态,然后内部把开关变成开的状态,但是返回值是给while的,所以返回值就是开关进来没操作之前的状态。

以下就是最终版本的自旋锁的伪代码:

bool lock = false; // 一开始没有锁上,任何线程都可以申请锁  
do {  
    while(test_and_set(&lock); // test_and_set 是一个原子操作
        Critical section  // 临界区
    lock = false; // 相当于释放锁,这样别的线程可以进入临界区
        Reminder section // 不需要锁保护的代码        
}

如果临界区的执行时间过长,使用自旋锁不是个好主意。之前我们介绍过时间片轮转算法,线程在多种情况下会退出自己的时间片。其中一种是用完了时间片的时间,被操作系统强制抢占。除此以外,当线程进行 I/O 操作,或进入睡眠状态时,都会主动让出时间片。显然在 while 循环中,线程处于忙等状态,白白浪费 CPU 时间,最终因为超时被操作系统抢占时间片。如果临界区执行时间较长,比如是文件读写,这种忙等是毫无必要的。

 

2.dispatch_semaphore(信号量  互斥类型)

先来介绍下Dispatch_semaphore的使用

dispatch_semaphore是GCD用来同步的一种方式,与他相关的共有三个函数,分别是

dispatch_semaphore_create,
dispatch_semaphore_signal,
dispatch_semaphore_wait。

下面我们逐一介绍三个函数:

dispatch_semaphore_create

dispatch_semaphore_t  dispatch_semaphore_create(long value);

传入的参数为long,输出一个dispatch_semaphore_t类型且值为value的信号量。

值得注意的是,这里的传入的参数value必须大于或等于0,否则dispatch_semaphore_create会返回NULL。

关于信号量,我就不在这里累述了,网上很多介绍这个的。我们这里主要讲一下dispatch_semaphore这三个函数的用法)。

dispatch_semaphore_signal的声明为: 

long dispatch_semaphore_signal(dispatch_semaphore_t dsema)

这个函数会使传入的信号量dsema的值加1;(至于返回值,待会儿再讲)

dispatch_semaphore_wait的声明为:  

long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);

这个函数会使传入的信号量dsema的值减1;

这个函数的作用是这样的,如果dsema信号量的值大于0,该函数所处线程就继续执行下面的语句,并且将信号量的值减1;

如果desema的值为0,那么这个函数就阻塞当前线程等待timeout(注意timeout的类型为dispatch_time_t,

不能直接传入整形或float型数),如果等待的期间desema的值被dispatch_semaphore_signal函数加1了,

且该函数(即dispatch_semaphore_wait)所处线程获得了信号量,那么就继续向下执行并将信号量减1。

如果等待期间没有获取到信号量或者信号量的值一直为0,那么等到timeout时,其所处线程自动执行其后语句。

dispatch_semaphore_signal的返回值为long类型

当返回值为0时表示当前并没有线程等待其处理的信号量,其处理

的信号量的值加1即可。当返回值不为0时,表示其当前有(一个或多个)线程等待其处理的信号量,并且该函数唤醒了一个等待的线程(当线程有优先级时,唤醒优先级最高的线程;否则随机唤醒)。

dispatch_semaphore_wait的返回值也为long型。当其返回0时表示在timeout之前,该函数所处的线程被成功唤醒。
当其返回不为0时,表示timeout发生。

 

在设置timeout时,比较有用的两个宏:DISPATCH_TIME_NOW 和 DISPATCH_TIME_FOREVER。

DISPATCH_TIME_NOW  表示当前;

DISPATCH_TIME_FOREVER  表示遥远的未来;

一般可以直接设置timeout为这两个宏其中的一个,或者自己创建一个dispatch_time_t类型的变量。

创建dispatch_time_t类型的变量有两种方法,dispatch_time和dispatch_walltime。

利用创建dispatch_time创建dispatch_time_t类型变量的时候一般也会用到这两个变量。

dispatch_time的声明如下:

dispatch_time_t dispatch_time(dispatch_time_t when, int64_t delta);

其参数when需传入一个dispatch_time_t类型的变量,和一个delta值。表示when加delta时间就是timeout的时间。

例如:dispatch_time_t  t = dispatch_time(DISPATCH_TIME_NOW, 1*1000*1000*1000);

表示当前时间向后延时一秒为timeout的时间。

关于信号量,一般可以用停车来比喻。  

停车场剩余4个车位,那么即使同时来了四辆车也能停的下。如果此时来了五辆车,那么就有一辆需要等待。

信号量的值就相当于剩余车位的数目,dispatch_semaphore_wait函数就相当于来了一辆车,dispatch_semaphore_signal

就相当于走了一辆车。停车位的剩余数目在初始化的时候就已经指明了(dispatch_semaphore_create(long value)),

调用一次dispatch_semaphore_signal,剩余的车位就增加一个;调用一次dispatch_semaphore_wait剩余车位就减少一个;

当剩余车位为0时,再来车(即调用dispatch_semaphore_wait)就只能等待。有可能同时有几辆车等待一个停车位。有些车主

没有耐心,给自己设定了一段等待时间,这段时间内等不到停车位就走了,如果等到了就开进去停车。而有些车主就像把车停在这,

所以就一直等下去。

参考文章  这里面有个Demo,理解不了的可以看一下,信号量1的时候一般都当做锁来看待

这个函数会使传入的信号量dsema的值减1;这个函数的作用是这样的,如果dsema信号量的值大于0,该函数所处线程就继续执行下面的语句,并且将信号量的值减1;如果desema的值为0,那么这个函数就阻塞当前线程等待timeout(注意timeout的类型为dispatch_time_t,不能直接传入整形或float型数),如果等待的期间desema的值被dispatch_semaphore_signal函数加1了,且该函数(即dispatch_semaphore_wait)所处线程获得了信号量,那么就继续向下执行并将信号量减1。如果等待期间没有获取到信号量或者信号量的值一直为0,那么等到timeout时,其所处线程自动执行其后语句。

dispatch_semaphore 是信号量,但当信号总量设为 1 时也可以当作锁来。在没有等待情况出现时,它的性能比 pthread_mutex 还要高,但一旦有等待情况出现时,性能就会下降许多。相对于 OSSpinLock 来说,它的优势在于等待时不会消耗 CPU 资源。

信号量实现内部代码:

int sem_wait (sem_t *sem) {  
  int *futex = (int *) sem;
  if (atomic_decrement_if_positive (futex) > 0)
    return 0;
  int err = lll_futex_wait (futex, 0);
    return -1;
)

首先会把信号量的值减一,并判断是否大于零。如果大于零,说明不用等待,所以立刻返回。具体的等待操作在 lll_futex_wait 函数中实现,lll 是 low level lock 的简称。这个函数通过汇编代码实现,调用到 SYS_futex 这个系统调用,使线程进入睡眠状态,主动让出时间片,这个函数在互斥锁的实现中,也有可能被用到。

主动让出时间片(互斥的做法,休眠)并不总是代表效率高。让出时间片会导致操作系统切换到另一个线程,这种上下文切换通常需要 10 微秒左右,而且至少需要两次切换。如果等待时间很短,比如只有几个微秒,忙等就比线程睡眠更高效。

 

 

3.pthread_mutex

互斥锁的实现原理与信号量非常相似,不是使用忙等,而是阻塞线程并睡眠,需要进行上下文切换。

pthread_mutexattr_t attr;  
pthread_mutexattr_init(&attr);  
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);  // 定义锁的属性

pthread_mutex_t mutex;  
pthread_mutex_init(&mutex, &attr) // 创建锁

pthread_mutex_lock(&mutex); // 申请锁  
    // 临界区
pthread_mutex_unlock(&mutex); // 释放锁  

对于 pthread_mutex 来说,它的用法和之前没有太大的改变,比较重要的是锁的类型,可以有 PTHREAD_MUTEX_NORMALPTHREAD_MUTEX_ERRORCHECKPTHREAD_MUTEX_RECURSIVE 等等,具体的特性就不做解释了,网上有很多相关资料。其中如果lock区域代码,再次进行加锁,就会变成递归,因此我们要把锁的属性变成PTHREAD_MUTEX_RECURSIVE来避免死锁

互斥锁的实现

 

互斥锁在申请锁时,调用了 pthread_mutex_lock 方法,它在不同的系统上实现各有不同,有时候它的内部是使用信号量来实现,即使不用信号量,也会调用到 lll_futex_wait 函数,从而导致线程休眠。

上文说到如果临界区很短,忙等的效率也许更高,所以在有些版本的实现中,会首先尝试一定次数(比如 1000 次)的 testandtest,这样可以在错误使用互斥锁时提高性能。

另外,由于 pthread_mutex 有多种类型,可以支持递归锁等,因此在申请加锁时,需要对锁的类型加以判断,这也就是为什么它和信号量的实现类似,但效率略低的原因。

 

4.NSLock,NSCondition,NSRecursiveLock

这些只是上面几个的上层封装,可以看上面提供的文章,这里不展开了

 

5.@synchronize (玉令天下传送门)

这其实是一个 OC 层面的锁, 主要是通过牺牲性能换来语法上的简洁与可读。

我们知道 @synchronized 后面需要紧跟一个 OC 对象,它实际上是把这个对象当做锁来使用。这是通过一个哈希表来实现的,OC 在底层使用了一个互斥锁的数组(你可以理解为锁池),通过对对象去哈希值来得到对应的互斥锁。具体介绍

其实这个哈希表的实现和weak属性一样,这里是以对象为key,然后互斥锁的数组为value,weak属性是以对象为key,指向该对象的weak指针为数组value,当对象dealloc的时候,调用一系列函数,找到该对象的value数组,把数组指针都置为nil,然后再把key对应的记录全部清空。

 

6.性能比较 (YY大神的一段代码,这里把这一坨全部拖到控制器即可,加了一个iOS10出现替换OSSpinLock的os_unfair_lock_t的锁)

//
//  ViewController.m
//  lock
//
//  Created by mintou on 2018/3/9.
//  Copyright © 2018年 mintou. All rights reserved.
//

#import "ViewController.h"

#import 
#import 
#import 
#import 


typedef NS_ENUM(NSUInteger, LockType) {
    LockTypeOSSpinLock = 0,
    LockTypedispatch_semaphore,
    LockTypepthread_mutex,
    LockTypeNSCondition,
    LockTypeNSLock,
    LockTypepthread_mutex_recursive,
    LockTypeNSRecursiveLock,
    LockTypeNSConditionLock,
    LockTypesynchronized,
    LockTypeUnfairLock,
    LockTypeCount,
};


NSTimeInterval TimeCosts[LockTypeCount] = {0};
int TimeCount = 0;

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    int buttonCount = 5;
    for (int i = 0; i < buttonCount; i++) {
        UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
        button.frame = CGRectMake(0, 0, 200, 50);
        button.center = CGPointMake(self.view.frame.size.width / 2, i * 60 + 160);
        button.tag = pow(10, i + 3);
        [button setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
        [button setTitle:[NSString stringWithFormat:@"run (%d)",(int)button.tag] forState:UIControlStateNormal];
        [button addTarget:self action:@selector(tap:) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview:button];
    }
}

- (void)tap:(UIButton *)sender {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self test:(int)sender.tag];
    });
}

- (void)test:(int)count {
    NSTimeInterval begin, end;
    TimeCount += count;
    
    {
        OSSpinLock lock = OS_SPINLOCK_INIT;
        begin = CACurrentMediaTime();
        for (int i = 0; i < count; i++) {
            OSSpinLockLock(&lock);
            OSSpinLockUnlock(&lock);
        }
        end = CACurrentMediaTime();
        TimeCosts[LockTypeOSSpinLock] += end - begin;
        printf("OSSpinLock:               %8.2f ms\n", (end - begin) * 1000);
    }
    
    
    {
        dispatch_semaphore_t lock =  dispatch_semaphore_create(1);
        begin = CACurrentMediaTime();
        for (int i = 0; i < count; i++) {
            dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
            dispatch_semaphore_signal(lock);
        }
        end = CACurrentMediaTime();
        TimeCosts[LockTypedispatch_semaphore] += end - begin;
        printf("dispatch_semaphore:       %8.2f ms\n", (end - begin) * 1000);
    }
    
    
    {
        pthread_mutex_t lock;
        pthread_mutex_init(&lock, NULL);
        begin = CACurrentMediaTime();
        for (int i = 0; i < count; i++) {
            pthread_mutex_lock(&lock);
            pthread_mutex_unlock(&lock);
        }
        end = CACurrentMediaTime();
        TimeCosts[LockTypepthread_mutex] += end - begin;
        pthread_mutex_destroy(&lock);
        printf("pthread_mutex:            %8.2f ms\n", (end - begin) * 1000);
    }
    
    
    {
        NSCondition *lock = [NSCondition new];
        begin = CACurrentMediaTime();
        for (int i = 0; i < count; i++) {
            [lock lock];
            [lock unlock];
        }
        end = CACurrentMediaTime();
        TimeCosts[LockTypeNSCondition] += end - begin;
        printf("NSCondition:              %8.2f ms\n", (end - begin) * 1000);
    }
    
    
    {
        NSLock *lock = [NSLock new];
        begin = CACurrentMediaTime();
        for (int i = 0; i < count; i++) {
            [lock lock];
            [lock unlock];
        }
        end = CACurrentMediaTime();
        TimeCosts[LockTypeNSLock] += end - begin;
        printf("NSLock:                   %8.2f ms\n", (end - begin) * 1000);
    }
    
    
    {
        pthread_mutex_t lock;
        pthread_mutexattr_t attr;
        pthread_mutexattr_init(&attr);
        pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
        pthread_mutex_init(&lock, &attr);
        pthread_mutexattr_destroy(&attr);
        begin = CACurrentMediaTime();
        for (int i = 0; i < count; i++) {
            pthread_mutex_lock(&lock);
            pthread_mutex_unlock(&lock);
        }
        end = CACurrentMediaTime();
        TimeCosts[LockTypepthread_mutex_recursive] += end - begin;
        pthread_mutex_destroy(&lock);
        printf("pthread_mutex(recursive): %8.2f ms\n", (end - begin) * 1000);
    }
    
    
    {
        NSRecursiveLock *lock = [NSRecursiveLock new];
        begin = CACurrentMediaTime();
        for (int i = 0; i < count; i++) {
            [lock lock];
            [lock unlock];
        }
        end = CACurrentMediaTime();
        TimeCosts[LockTypeNSRecursiveLock] += end - begin;
        printf("NSRecursiveLock:          %8.2f ms\n", (end - begin) * 1000);
    }
    
    
    {
        NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:1];
        begin = CACurrentMediaTime();
        for (int i = 0; i < count; i++) {
            [lock lock];
            [lock unlock];
        }
        end = CACurrentMediaTime();
        TimeCosts[LockTypeNSConditionLock] += end - begin;
        printf("NSConditionLock:          %8.2f ms\n", (end - begin) * 1000);
    }
    
    
    {
        NSObject *lock = [NSObject new];
        begin = CACurrentMediaTime();
        for (int i = 0; i < count; i++) {
            @synchronized(lock) {}
        }
        end = CACurrentMediaTime();
        TimeCosts[LockTypesynchronized] += end - begin;
        printf("@synchronized:            %8.2f ms\n", (end - begin) * 1000);
    }
    
    {
        os_unfair_lock_t unfairLock;
        unfairLock = &(OS_UNFAIR_LOCK_INIT);
        
        begin = CACurrentMediaTime();
        for (int i = 0; i < count; i++) {
            os_unfair_lock_lock(unfairLock);
            os_unfair_lock_unlock(unfairLock);
        }
        end = CACurrentMediaTime();
        TimeCosts[LockTypeUnfairLock] += end - begin;
        printf("os_unfair_lock:           %8.2f ms\n", (end - begin) * 1000);
    }
    printf("---- fin (%d) ----\n\n",count);
}

@end

根据上面的代码,大家需要的可以自己全部贴到控制器进行测试,一下就是根据加锁解锁次数进行了测试

OSSpinLock:                   0.04 ms
dispatch_semaphore:           0.02 ms
pthread_mutex:                0.04 ms
NSCondition:                  0.03 ms
NSLock:                       0.03 ms
pthread_mutex(recursive):     0.04 ms
NSRecursiveLock:              0.05 ms
NSConditionLock:              0.10 ms
@synchronized:                0.12 ms
os_unfair_lock:               0.03 ms
---- fin (1000) ----
OSSpinLock:                   0.26 ms
dispatch_semaphore:           0.31 ms
pthread_mutex:                0.43 ms
NSCondition:                  0.43 ms
NSLock:                       0.47 ms
pthread_mutex(recursive):     0.71 ms
NSRecursiveLock:              0.87 ms
NSConditionLock:              1.05 ms
@synchronized:                1.70 ms
os_unfair_lock:               0.18 ms
---- fin (10000) ----
OSSpinLock:                 115.63 ms
dispatch_semaphore:         155.67 ms
pthread_mutex:              222.16 ms
NSCondition:                214.12 ms
NSLock:                     246.62 ms
pthread_mutex(recursive):   357.11 ms
NSRecursiveLock:            465.78 ms
NSConditionLock:            739.09 ms
@synchronized:             1312.10 ms
os_unfair_lock:             138.24 ms
---- fin (10000000) ----

OSSPinLock,dispatch_semaphore的性能远远优于其他的锁,但是OSSpinLock由于优先级反转的问题,苹果在iOS 10的时候推出了os_unfair_lock来替代,而且性能不减当年,但是要在iOS 10之后才能用(虽然自旋锁的性能优于互斥锁),可以看出@synchronize和NSConditionLock明显性能最差,如果项目中对性能特别敏感,建议使用dispatch_semaphore,如果基于方便的话就用@synchronize就可以了
 

 

 

 

 

 

你可能感兴趣的:(基础知识,iOS性能优化系列)