iOS中的锁和线程同步
2020-10-27 本文已影响0人
康小曹
线程同步和iOS中的锁
一、自旋锁(OSSpinLock)
- 一直占用cpu,相当于while循环
- 已经不被推荐,因为可能会产生优先级反转;
优先级反转
- 多任务的调度有许多算法,常见的有FIFO、优先级、时间片轮询、短任务优先;
- 系统一般采取混合算法,因为不同进程特点不一样,单一的调度算法不能满足所有的场景;比如短任务算法需要在线程之间的切换消耗 和 任务真正执行的消耗,两者之间平衡,所以,每种调度算法都有自己的缺点和有点,混合算法是最高效的;
- 自旋锁在低等级的任务加锁之后占用了高等级任务所需要的资源。根据算法,如果中等级的任务不需要使用到这个被占用的资源,结果就是中等级任务分配的时间可能会多于高等级;
- 以上是高等级任务在等待时可以休眠的情况,也就是等待不是很占用cpu,这种情况下只是出现优先级反转,但是如果高优先级任务的等待是忙循环,也就会出现cpu把事件都分配给高级别任务,但是高级别任务又是忙循环,低级别任务无法获取cpu时间,最终就是死循环,系统可能会崩溃;
二、os_unfair_lock
- 休眠,不是忙等待,是互斥锁;
- iOS10之后才能使用,用于替换自旋锁;
三、pthread_mutex
- 跨平台;
- 互斥锁,等待属于休眠;
- 可以设置类型,有:默认(PTHREAD_MUTEX_NORMAL)、递归所(PTHREAD_MUTEX_RECUSIVE)、错误检查锁(PTHREAD_MUTEX_ERRORCHECK);
- normal类型的锁在创建时(pthread_mutex_init(&mutex,0))传空即可,另外两种锁需要使用pthread_mutex_attr_settype来设置;
- 需要使用destory进行销毁,attr、condition、mutex都需要销毁;
- 递归锁在递归时使用不会造成死锁问题;
- 递归锁:允许同一个线程对通一把锁重复加锁,加锁次数和解锁次数对应时才能解锁;
- 条件锁:pthread_cond_wait 和 pthread_cond_signal 配合使用;pthread_cond_wait(cond,mutex)会标记cond并解锁mutex,线程进入休眠,并且阻塞代码。当调用pthread_cond_signal(cond)之后,接收到信号后再对mutex进行加锁,然后继续执行代码;
- 条件所中的cond并不是类似于if else,而只是一个标记;
- 条件所一般用于解决线程以来问题,当一个线程任务的执行需要以来另一个线程任务的执行时使用,条件起到的是标记和传递的作用;
- 唤醒一个线程的等待使用pthread_cond_signal,唤醒所有等待的线程则使用广播方法:pthread_cond_broadcast
- 条件等待时,调用signal方法发送信号之后,wait方法其实不是只判断一次的,而是循环判断检测,如果这个锁没加锁成功,就继续等待,如果加锁成功就返回,继续执行后面的代码;所以,signal和unlock方法的调用顺序差别不大,只有在两者调用期间出现很大的延时操作时,wait方法会等待很久才加锁成功,继续执行后面代码。总之,逻辑不会受影响,只是时间问题,pthread_mutex内部已经考虑到这种问题,完成了解决方案的实现;
四、自旋锁和互斥锁
- step:按行执行代码;si:按行执行汇编代码;
- 自旋锁:忙等,相当于while循环,在汇编中的体现是:jne -> 0xxxxxx ,条件成立则跳转到指定地址循环执行;
- 互斥锁:等待时休眠,不占用cpu,类似于RunLoop;本质是:syscall,调用系统级别的函数去休眠并等待被唤醒的消息;
- OSSpinLoc是自旋锁,os_unfair_lock和pthread_mutex是互斥锁;
五、NSLock、NSRecusiveLock、NSCondition、NSConditionLock
- 是对pthread_mutex的封装,封装成了OC对象;
六、串行队列
- async和sync决定是否可以开启线程,队列表示任务的执行方式。
- 任务是一代码块为最小颗粒,而不是以一行代码为最小颗粒。代码块中的每一行代码默认都是同步执行;
- sync表示从队列中取出任务到当前线程执行,async表示从队列中取出任务异步执行。所以,如果sync+serial queue 结果就是:可以将需要同步的任务(抢占资源的任务)添加到同一个queue中然后同步执行,结果就是线程同步成功;
七、dispatch_semaphore
- dispatch_semaphore在初始化时需要使用create传入一个最大信号量;
- 最大信号量意味着最多又多少条线程可以同时执行,如果是1,那么就类似于串行队列,所以也可以实现线程同步
dispatch_semaphore_wait(semaphore,time)的作用:
- 当semaphore>0时,让当前线程直接继续执行,且dispatch_semaphore的值-1;
- 当semaphore<=0时,让当前线程阻塞在这句代码中,且让线程休眠等待;
- time表示如果需要等待,那么最长等多久需要返回,传now立马进行判断,传forever就一直等;
dispatch_semaphore_signal(semaphore)的作用:
- 让semaphore+1;
使用流程:
- 比如创建semaphore设置的最大信号量是5,有100个线程会执行到这句代码;
- 当第一次进入时,semaphore=5>0,所以就会让当前进入,继续执行并且dispatch_semaphore-1=4;
- 假设dispatch_semaphore_wait和dispatch_semaphore_signal之间的代码时耗时操作;
- 以此类推,当第五条线程进入之后,semaphore=0,而因为是耗时操作,所以100条线程都会执行到dispatch_semaphore_wait代码;
- 从第六条线程开始,因为dispatch_semaphore=0,线程被阻塞,剩余的95条线程如果在耗时操作没有完成之前,也就是没有调用dispatch_semaphore_signal之前,如果执行到dispatch_semaphore_wait,那么都会处于休眠等待的状态;
- 当最先进入的线程执行完耗时操作,调用dispatch_semaphore_signal,dispatch_semaphore+1>0,那么第六条线程就会从休眠中唤醒,继续执行后面的代码;
- 以此类推…所以当semaphore初始化时设置为1,那么只有一条线程能够访问共享资源,如此便实现了线程同步;
八、@synchronized
- 底层仍然是pthread_mutex,是使用的recusive创建的递归锁;
- 根据@synchronized(obj){}传入的对象,生成一个和对象对应的一把锁,存入到map表中;
- 关键字不能自动提示,因为存在性能问题,不被推荐使用;
九、性能对比