野火🔥

生命如野火,骄傲而顽强

Hello 2018

写了两篇17年的感悟,这一篇写一下对18年的一些期望。

新年寄语

突破、胸怀、守时
我希望,这三个寄语,能贯穿一年的主线,引领我一年的成长。

突破是我对一年最大的期望了。
18年3月27日,是我头条入职1周年,也是我毕业参加工作4整年的日子。毕业后先去了微信,然后是好奇心日报,再到头条,作为一个iOS开发,已经4个年头,虽然成为了一个最基层的技术领导者,但瓶颈也随之出现。
今年,努力寻找在工作上肩负起更大的责任,或者在技术方向上寻找一个全新的领域。

胸怀一个男人应有的素质
18年,我即将会组建家庭,标志的成为一个完整的自然人,有道是成家立业。
随之而来的,是家庭生活,双方长辈的关系,琐事和纠纷也许也会不期而至。
有事做得好,有时做的差,其实都是家人,影响本身不大,重要的更多是态度。作为一个男人,在家庭中,尤其要有包容的胸怀,有心胸,能包容女人的脾气,能在婆媳中增加缓冲,永远起到积极的作用。
嗯,28年来,心胸这个词第一次进入了我的个人要求中,提到这个词,能感觉到自己的成长。

守时是品德,想表达的更多是一些传统素质
守信,应该是立于世界的基本品质,自认为一直坚持的不错,或者并不需要坚持,只需要不被错误引导即可。
但守时,毕业后渐渐堕落,和大家同流。虽有太多理由可以原谅自己。但我依旧相信,生活中有些东西,值得一直坚持,18年,言而有信从守时开始做起。

2017年的几点感悟

一年又在没有一丝丝防备中到来而消去,回首一年,感受平静河流下面蕴含的一些汹涌,每天不知所踪的忙碌。
随波逐流,又想特立独行;有时怀疑自己,有时信心膨胀;会因别人转瞬的表情而患得患失。索性,那份全力以赴的热情,还未流失。

写这篇文章,主要描述一种感觉,沉淀一下2017年给自己带来的几点感悟,也希望指引一下有些迷茫的自己,寻找下一个突破

一、初窥堂奥——一个技术领导者的起步

跳槽还是不期而至,在好奇心日报没能坚持到第三年,之前写过一篇总结,回顾了跳槽的心路历程。
头条是一家不错的公司,起码福利和待遇还是不错的。选择来头条,是因为相信这是一家一定会成功的公司,而自己,在这里有一个在个人职业阅历下,能拥有的还不错的坑。

17年的总结

对我个人来讲,17年有两大收获,一是跳槽来了头条,二是找到了人生的另一半。
本该是事业有进步,爱情有收获,但发生了好多别的事情,使得这一年其实有些痛苦和无奈。

衰老

妈妈去年底的体检查出了宫颈癌病毒,我刚看到体检报告的时候一下子就懵了,后来发现其实没那么严重,上半年做了个锥切手术,但未能完全痊愈,需要持续观察才行,每念此处,都心绞一阵。
妈妈前几年血糖比较高,导致眼底出血,虽然做了激光手术,但也留下了永久的后遗症,前几天回家,发现妈妈视力越发的不好了,心疼。
由于我父亲,今年下半年父母发生了非常大的争吵,其中细节无心评述。心里对父亲充满怨念,包括上面说的妈妈两个病因,父亲都有直接或间接原因,也许我永远都不会原谅他了,看着他,心里总带着烦躁和鄙视。

投资

妈妈去年年底像魔障了一样,手里稍微有点钱就想加大杠杆投资房产,在国家政策最为严厉的封口浪尖的4月份,依然坚持买了个门市楼,虽然后来她非常后悔,但木已成舟,悔之晚矣。
去年底今年初的慌乱房产投机,损失100多万,而且,彻底毁掉了一家三口人的现金流,同时背负了沉重的贷款债务,今年多次在睡前想起,然后辗转反侧难以入睡。
最终贷款压力主要在我身上,父母已老,不想太多说什么,只是这几年要对不住邹韵了。索性,妈妈陆续卖掉一些房产,略微缓解部分燃眉。

突破

还算幸运,今年职场上再获一次突破,从好奇心日报的一个小技术团队的负责人,到了头条的一个小技术团队的技术leader,麻雀虽小,却收获颇多。
因为相信头条一定最后能成为一家了不起的公司,相信如果个人能随着公司的成长,会有更大的进步。
一年来,学会了一些流程、使用了一些工具、也了解了一些运转方式,有的东西很好玩,有的东西很无奈。
软实力提高很多,硬实力提高比较少,工作很忙,有时迷失了自己。
但尚好,没有盲目尊大,也没有妄自菲薄。
期望,能找到下一个突破。

爱情

儿时好友的婚讯给了我不小的刺激,人有时就是不知道自己最终要的是什么,便如人生哲学有教育别人“今朝有酒今朝醉”,让人及时行乐;也有告诫人们“少壮不努力,老大徒伤悲”的励志警句。很多事情,回忆今天,难保会有遗憾和悔恨。
说遍了无病呻吟,今年的爱情其实是很顺利的,认识了邹韵,然后住在了一起,虽有时激情不高,但却和谐美好,邹韵是个可以白头的女孩,我很满意,也很开心。不管酸甜苦辣,愿能永远。

有点成绩?

  • 因为换工作融入比较好,团队给力,所以有了一个还不错的绩效。但其实工作成果自己是不够满意的。
  • 爱情基本稳定,即将走向婚姻
  • 买了个还挺满意的房子,生活可以期待
  • 开了大半年女朋友的车,生活体验高了不少

UIResponder 响应链 拾遗

背景

基于ResponderChain可以做一些事件传递,将所有view的操作最终都送回到ViewController来处理,以实现view的功能单一性。如下:

@interface UIResponder (Router)
- (void)routerEventWithName:(NSString *)eventName userInfo:(NSDictionary *)userInfo;
@end

@implementation UIResponder (Router)
- (void)routerEventWithName:(NSString *)eventName userInfo:(NSDictionary *)userInfo
{
    [[self nextResponder] routerEventWithName:eventName userInfo:userInfo];
}
@end

每个cell在做事件请求时,只需要调用routerEventWithName:userInfo:,即可将请求传递给对应的ViewController(也可以是响应链任意一环),然后用EventName来区分事件类型。

ChildViewController

一般来讲 view.nextResponder = view.superView。ViewController的root view的nextResponder是当前ViewController,那childViewController的view的nextResponder是谁呢?如果用nextResponder拦截,childViewController能拦截到其中的view的响应吗?

XCode实测:

  • 如果addChildViewController:

childVC.view.nextResponder == childVC,childVC.nextResponder == childVC.view.superView,相当于在subView&superView的响应联调中,插入了一个VC,与window和VC.view的关系是一致的。

博客继续记录

博客继续记录

3月底入职了头条,博客戛然而止。太忙了...

以前写了太多的技术上的东西,其实生活不仅仅有代码,以后多一些扯淡

博客图片换了google的图床,嗯,想看肯定要有梯子。

域名又买了5年,才80。

评论在纠结于多说和网易云跟帖的关闭困境之后,终于找到了合适的替代品,机于github的issues的gitment应该是最优解。

嗯 最重要的,以后还要多写点博客。

简单处理不小心调用高版本API的问题

iOS API 版本检查

如果app兼容2个以上版本,那就需要注意低版本不能调用高版本API,为避免开发过程中不小心调用,必要的工具检查是必须的,下面介绍一种简单的方法处理。

原理 ---- Clang

2015 年 clang 本身增加 API 版本检查功能,通过 -Wpartial-availability 这个 flag 可以打开,后续版本把 flag 变成了 -Wunguarded-availability。打开之后,clang 会把所有在低版本 iOS 上调用高版本 API 的情况用 warning 暴露出来,如下图所示。

具体实现

  • 1、打开 -Wunguarded-availability 在调用高版本API时候报warning,为避免warning过多而忽视,用 -Werror-unguarded-availability 标记强制编译不过。
  • 2、针对pod需要在 podspec 中添加compiler_flags
  • 3、如果代码本身安全(使用了 respondsToSelector: 保护),可以用 #pragma clang diagnostic ignored 的方式压掉警告,或者 @available 直接解决,如下图。

关于Product Owner的思考

关于Product Owner的思考

Product Owner是Scrum里面的概念,上周末和able哥吃饭听到了这个概念,最近看了一些相关的文章,得到了一些收获。

  1. 基本的能力,锻炼自己把技术问题抽象化,说出来让产品能理解的能力
  2. 了解项目背景,通过充分沟通摸索出一个需求的核心目标,了解到哪些是可以妥协的点,哪些是坚持的点,这样在遇到技术困难的时候可以从技术投入成本和产品产出收益(注意,这里不是技术收益)权衡是否需要提出来,如果提出来是否准备好了妥善的解决方案,有方案意见被接纳的可能性会增大
  3. 主动了解项目核心目标和关键性指标,了解策略和方案,了解 A/B 测试的分组策略和实验数据观察
  4. 了解产品做事的方式,他们是至上往下做产品,目标导向,我们技术在看产品设计的时候是一个反向的过程,本来就是在一个信息和理解不对称的过程,我们看到的是结果,看不到过程(就像我们看别人代码是一样的过程),这个时候要通过文档,沟通等方式去增强理解。产品 sense 不是一下就能提升的,同理心很重要。

多说换成网易云跟帖,评论都丢了

多说关闭了,换个替代品,网易出品,应该不会没事就倒闭吧。

评论都导不出来了,不过也不是很多,以后评论从新开始吧。


10月更新

没想到网易云跟帖也关闭了,以后彻底换disqu了,图床也换google了,不理墙内用户了。

再见 好奇心日报

昨天下午,收到了人生第二份《离职证明》,我也正式在法律上不再属于好奇心日报了。

写这篇文章,缅怀一下这两年的时光,因为这段工作的结束需要一个仪式感。
同时也鼓励我下一段工作,希望接下来在头条依然能够保持旺盛的学习和工作热情,保持谦卑和自省的态度。
祝愿好奇心日报越来越好!

和好奇心日报的纠缠源于很早的外包,14年的一个周六晚上,中秋节前后,印象中天气很热,在TIT南边巷子里吃麻辣烫,广州麻辣烫的独有味道直到现在还让我流出口水。徐源说有个Android外包要做,做不?我说,当然做。那会儿事情不多,微信的工作虽然忙碌但压力不大,而且很规律。

【iOS】从同步锁到多线程

年前年后,忙的事情太多,没有继续更新博客,打算近期总结下过往。

如果每天只是做需求,写UI,可能不需要知道太多多线程的知识。也许简单的GCD会使用,再加几个开源框架,大多数功能都能实现。但如果你想写个框架,开源出来,那多线程就是一个永远绕不开的话题。除非你是一个前端开发者,否则无论在公司里,还是供自己娱乐,如果你想把技术走向深入,那多线程都是必须要明白的事。

本文先从iOS中objective-c的多线程同步开始,给iOS下的多线程做一个概述。

一、几种线程同步方式

1、自旋锁 OSSpinLock

上测试代码

__block OSSpinLock oslock = OS_SPINLOCK_INIT;
    
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
   NSLog(@"线程2 准备上锁");
   OSSpinLockLock(&oslock);
   NSLog(@"线程2");
   OSSpinLockUnlock(&oslock);
   NSLog(@"线程2 解锁成功");
});
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
   NSLog(@"线程1 准备上锁");
   OSSpinLockLock(&oslock);
   NSLog(@"线程1 sleep");
   sleep(4);
   NSLog(@"线程1");
   OSSpinLockUnlock(&oslock);
   NSLog(@"线程1 解锁成功");
});

虽然YY大神http://blog.ibireme.com/说其已经不再安全,但GCD在多线程实际应用过程中,未发现问题,并行线程只要获取到oslock,其它线程一律阻塞(非睡眠),直到之前获取的解锁为止,上述代码QUEUE优先级相差较大,在实际使用中未发生高优先级忙等状态。

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

可以在同一线程无限制上锁,但必须成对出现解锁,否则会死锁。可以在同一线程无限制调用解锁,但如果没有获取锁,解锁代码无效。

结论:QUEUE的优先级和CPU调度的线程优先级可能并不是一回事,实际运用GCD来进行多线程开发时,可以应用自旋锁进行数据同步。

2、信号量 dispatch_semaphore

上测试代码

dispatch_semaphore_t signal = dispatch_semaphore_create(1);
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 3.0f * NSEC_PER_SEC);
    
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
   NSLog(@"线程1 等待ing");
   dispatch_semaphore_wait(signal, timeout); //signal 值 -1
   NSLog(@"线程1 sleep");
   sleep(2);
   NSLog(@"线程1");
   dispatch_semaphore_signal(signal); //signal 值 +1
   NSLog(@"线程1 发送信号");
});
    
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
   NSLog(@"线程2 等待ing");
   dispatch_semaphore_wait(signal, timeout);
   NSLog(@"线程2 sleep");
   sleep(2);
   NSLog(@"线程2");
   dispatch_semaphore_signal(signal);
   NSLog(@"线程2 发送信号");
});

信号量dispatch_semaphore被称为spinlock的替代方案。(引http://blog.ibireme.com/2016/01/16/spinlock_is_unsafe_in_ios/
使用方法非常简单,dispatch_semaphore_create(1)为创建信号,数字表示可以同时几个线程使用信号。为1表示同步使用。上述代码如果此处标2就和没设置信号量一样,并发自行运行。如果设置为0,则一律等待overTime时自动释放,所有代码都不执行,理论上也具有同步作用,就是慢点...
dispatch_semaphore_wait中传入的timeout表示最长加锁时间,此处sleep如果为4,则在3s后会自动释放锁,其它线程可以获取信号并继续运行。

和厕所坑位类似,dispatch_semaphore_create(1)表示只有1个坑位,timeout = dispatch_time(DISPATCH_TIME_NOW, 3.0f * NSEC_PER_SEC)表示坑位只能占用3秒。无论是否当前线程,坑位都一致。可以在同一线程频繁调用dispatch_semaphore_wait,在只有一个坑位且没有dispatch_semaphore_signal信号情况下,会等到每次的timeout。所以理论上可以不成对出现。

semaphore ['sɛməfɔr] : 信号;旗语;

3、互斥锁 pthread_mutex

看测试代码

static pthread_mutex_t pLock;
pthread_mutex_init(&pLock, NULL);
    
//1.线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
   NSLog(@"线程2 准备上锁");
   pthread_mutex_lock(&pLock);
   NSLog(@"线程2");
   pthread_mutex_unlock(&pLock);
});
    
//2.线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
   NSLog(@"线程1 准备上锁");
   pthread_mutex_lock(&pLock);
   sleep(3);
   NSLog(@"线程1");
   pthread_mutex_unlock(&pLock);
});

pthread 表示 POSIX thread,定义了一组跨平台的线程相关的 API,pthread_mutex 表示互斥锁。
使用上没啥说的,测试效果和上文一致。
非递归锁,同一线程重复调用加锁会造成死锁。

pthread_mutex(recursive) 递归锁

测试代码

static pthread_mutex_t pLock;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr); //初始化attr并且给它赋予默认
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); //设置锁类型,这边是设置为递归锁
pthread_mutex_init(&pLock, &attr);
pthread_mutexattr_destroy(&attr); //销毁一个属性对象,在重新进行初始化之前该结构不能重新使用
    
//1.线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
   static void (^RecursiveBlock)(int);
   RecursiveBlock = ^(int value) {
       pthread_mutex_lock(&pLock);
       if (value > 0) {
           NSLog(@"value: %d", value);
           RecursiveBlock(value - 1);
       }
       
   };
   NSLog(@"线程1 准备上锁");
   RecursiveBlock(5);
   NSLog(@"线程1");
   pthread_mutex_unlock(&pLock);
   NSLog(@"线程1 解锁");
});
    
//2.线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
   NSLog(@"线程2 准备上锁");
   pthread_mutex_lock(&pLock);
   NSLog(@"线程2");
   pthread_mutex_unlock(&pLock);
   NSLog(@"线程2 解锁");
});

递归锁比较安全,可以认为同一线程加且仅加一次锁,重复加锁不会造成死锁。无论同一线程加锁多少次,解锁1次即可。

4&5、NS前缀的两个锁NSLockNSRecursiveLock

使用比较简单,效果基本对应上述两种情况

NSLock *lock = [NSLock new];
[lock lock];
NSLog(@"加锁运行");
[lock unlock];
    
NSRecursiveLock *recursiveLock = [NSRecursiveLock new];
[recursiveLock lock];
NSLog(@"加锁运行");
[recursiveLock unlock];

NS开头的类都是对CoreFoundation的封装,只是易用一些。NSRecursiveLock为递归锁,可以在循环和递归中使用。
这里NSLock和NSRecursiveLock都是封装的互斥锁pthread_mutex
NSLock 只是在内部封装了一个 pthread_mutex,属性为 PTHREAD_MUTEX_ERRORCHECK,它会损失一定性能换来错误提示。理论上 NSLock 和 pthread_mutex 拥有相同的运行效率,实际由于封装的原因,会略慢一点,由于有缓存存在,相差不会很多,属于相同数量级。
NSRecursiveLock 与 NSLock 的区别在于内部封装的 pthread_mutex_t 对象的类型不同,NSRecursiveLock 的类型为 PTHREAD_MUTEX_RECURSIVE。

6、NSCondition

顾明思意,条件锁,一种生产者---消费者模型。
它通常用于标明共享资源是否可被访问或者确保一系列任务能按照指定的执行顺序执行。如果一个线程试图访问一个共享资源,而正在访问该资源的线程将其条件设置为不可访问,那么该线程会被阻塞,直到正在访问该资源的线程将访问条件更改为可访问状态或者说给被阻塞的线程发送信号后,被阻塞的线程才能正常访问这个资源。

7、条件锁 NSConditionLock

这里和NSLock主要区别是增加了一个NSInteger类型的condition参数,api很简单,也很少。condition就是一个条件标识。在加锁和解锁时对NSConditionLock做条件判断和修改,相当于if语句。

NSConditionLock *cLock = [[NSConditionLock alloc] initWithCondition:0];
    
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
   [cLock lockWhenCondition:1];
   NSLog(@"线程2");
   [cLock unlockWithCondition:3];
});
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
   if([cLock tryLockWhenCondition:0]){
       NSLog(@"线程1");
       [cLock unlockWithCondition:1];
   }else{
       NSLog(@"失败");
   }
});
    
//线程3
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
   [cLock lockWhenCondition:3];
   NSLog(@"线程3");
   [cLock unlockWithCondition:2];
});

上述代码会按照:线程1-->线程2-->线程3按顺序执行。

实际的实现原理就是里面封装了一个NSCondition对象,在lock时判断NSCondition对象的条件是否满足,不满足则wait,unlock时对发送NSCondition的broadcast,属于一个常见的生产者--消费者模型。

8、简单易用的条件锁 @synchronized

@synchronized (self) {
   NSLog(@"加锁运行");
}

只要关键字中的对象一致,则多个线程会互斥等待程序运行完成。
@synchronized 实际上是把修饰对象当做锁来使用。这是通过一个哈希表来实现的,OC 在底层使用了一个互斥锁的数组(你可以理解为锁池),通过对对象去哈希值来得到对应的互斥锁。

二、5S下测试一千万次加锁解锁时间测试

注意 :线程锁性能的加锁和解锁耗时并不能准确反映锁的效率(因为锁的效率还要综合考虑CPU时间片切换),它只能从一定程度上反映锁的实现复杂度。

上代码:

#define ITERATIONS (10000000) // 1千万
+ (void)test
{
    double then, now;
    
    @autoreleasepool {
        
        // 普通锁 NSLock
        NSLock *lock = [NSLock new];
        then = CFAbsoluteTimeGetCurrent();
        for(NSInteger i = 0; i < ITERATIONS; ++i)
        {
            [lock lock];
            [lock unlock];
        }
        now = CFAbsoluteTimeGetCurrent();
        NSLog(@"NSLock: %f sec\n", now-then);
        
        // 互斥锁 pthread_mutex
        pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
        then = CFAbsoluteTimeGetCurrent();
        for(NSInteger i = 0; i < ITERATIONS; ++i)
        {
            pthread_mutex_lock(&mutex);
            pthread_mutex_unlock(&mutex);
        }
        now = CFAbsoluteTimeGetCurrent();
        NSLog(@"pthread_mutex: %f sec\n", now-then);
        
        // 递归锁 pthread_mutex(recursive)
        static pthread_mutex_t pLock;
        pthread_mutexattr_t attr;
        pthread_mutexattr_init(&attr); //初始化attr并且给它赋予默认
        pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); //设置锁类型,这边是设置为递归锁
        pthread_mutex_init(&pLock, &attr);
        pthread_mutexattr_destroy(&attr); //销毁一个属性对象,在重新进行初始化之前该结构不能重新使用
        
        then = CFAbsoluteTimeGetCurrent();
        for(NSInteger i = 0; i < ITERATIONS; ++i)
        {
            pthread_mutex_lock(&pLock);
            pthread_mutex_unlock(&pLock);
        }
        now = CFAbsoluteTimeGetCurrent();
        NSLog(@"pthread_mutex(recursive): %f sec\n", now-then);
        
        // 自旋锁 OSSpinlock
        OSSpinLock spinlock = OS_SPINLOCK_INIT;
        then = CFAbsoluteTimeGetCurrent();
        for(NSInteger i = 0; i < ITERATIONS; ++i)
        {
            OSSpinLockLock(&spinlock);
            OSSpinLockUnlock(&spinlock);
        }
        now = CFAbsoluteTimeGetCurrent();
        NSLog(@"OSSpinlock: %f sec\n", now-then);
        
        // synchronized
        id obj = [NSObject new];
        then = CFAbsoluteTimeGetCurrent();
        for(NSInteger i = 0; i < ITERATIONS; ++i)
        {
            @synchronized(obj)
            {
            }
        }
        now = CFAbsoluteTimeGetCurrent();
        NSLog(@"@synchronized: %f sec\n", now-then);
        
        // dispatch_semaphore
        dispatch_semaphore_t lockSemaphore = dispatch_semaphore_create(1);
        then = CFAbsoluteTimeGetCurrent();
        for(NSInteger i = 0; i < ITERATIONS; ++i)
        {
            dispatch_semaphore_wait(lockSemaphore, DISPATCH_TIME_FOREVER);
            dispatch_semaphore_signal(lockSemaphore);
        }
        now = CFAbsoluteTimeGetCurrent();
        NSLog(@"dispatch_semaphore: %f sec\n", now-then);
        
        
        NSCondition *cLock = [NSCondition new];
        then = CFAbsoluteTimeGetCurrent();
        for(NSInteger i = 0; i < ITERATIONS; ++i)
        {
            [cLock lock];
            [cLock unlock];
        }
        now = CFAbsoluteTimeGetCurrent();
        NSLog(@"NSCondition: %f sec\n", now-then);
        
        
        NSRecursiveLock *rLock = [NSRecursiveLock new];
        then = CFAbsoluteTimeGetCurrent();
        for(NSInteger i = 0; i < ITERATIONS; ++i)
        {
            [rLock lock];
            [rLock unlock];
        }
        now = CFAbsoluteTimeGetCurrent();
        NSLog(@"NSRecursiveLock: %f sec\n", now-then);
        
        // dispatch_barrier_async
        dispatch_queue_t queue = dispatch_queue_create("xyz.chaisong.lock", DISPATCH_QUEUE_SERIAL);
        then = CFAbsoluteTimeGetCurrent();
        for(NSInteger i = 0; i < ITERATIONS; ++i)
        {
            dispatch_barrier_async(queue, ^{
            });
        }
        now = CFAbsoluteTimeGetCurrent();
        NSLog(@"dispatch_barrier_async: %f sec\n", now-then);
    }
}

上述的dispatch_barrier_async只是GCD的一种数据同步方案,并不属于锁,这里只比较多线程同步方案的效率。

结论,单从效率上来看dispatch_barrier_async@synchronized差的比较多,不建议使用,其它整体相差不大;相同类型的锁递归锁和普通锁效率相差接近一倍,如果不会在循环或者递归中频繁使用加锁和解锁,不建议使用递归锁;OSSpinlock各路大神都说有问题,从效率上讲,建议用互斥锁pthread_mutex(YYKit方案)或者信号量dispatch_semaphore(CoreFoundation和protobuf方案)作为替代。
OSpinlock为什么效率奇高主要原因是:并没有进入系统kernel,使用它可以节省系统调用和上下文切换。

三、一些多线程的基础知识

1、时间片轮转调度算法

了解多线程加锁必须知道时间片轮转调度算法,才能深切理解其原理、性能瓶颈。
现代操作系统在管理普通线程时,通常采用时间片轮转算法(Round Robin,简称 RR)。每个线程会被分配一段时间片(quantum),通常在 10-100 毫秒左右。当线程用完属于自己的时间片以后,就会被操作系统挂起,放入等待队列中,直到下一次被分配时间片,如果线程在时间片结束前阻塞或结束,则CPU当即进行切换。由于线程切换需要时间,如果时间片太短,会导致大量CPU时间浪费在切换上;而如果这个时间片如果太长,会使得其它线程等待太久;

2、原子操作

狭义上的原子操作表示一条不可打断的操作,也就是说线程在执行操作过程中,不会被操作系统挂起,而是一定会执行完(理论上拥有CPU时间片无限长)。在单处理器环境下,一条汇编指令显然是原子操作,因为中断也要通过指令来实现,但一句高级语言的代码却不是原子的,因为它最终是由多条汇编语言完成,CPU在进行时间片切换时,大多都会在某条代码的执行过程中。
但在多核处理器下,则需要硬件支持,没了解过具体实现。

3、自旋锁和互斥锁

都属于CPU时间分片算法下的实现保护共享资源的一种机制。都实现互斥操作,加锁后仅允许一个访问者。
却别在于自旋锁不会使线程进入wait状态,而通过轮训不停查看是否该自旋锁的持有者已经释放的锁;对应的,互斥锁在出现锁已经被占用的情况会进入wait状态,CPU会当即切换时间片。

自旋锁实现原理

简单的while循环

lock = 0;
do{
    while(test_and_set(&lock));
    临界区
    lock = 0;
    其余部分
} while(1)

test_and_set用来保证条件判断的原子性操作,lock为旗标。
自旋锁的一大缺陷是会使得线程处于忙等状态。因为如果临界区执行时间过长,其它线程就会在当前整个时间片一直处于忙等状态,浪费大量CPU时间。所以,如果临界区时间很短,可以使用自旋锁,否则建议使用互斥锁。

互斥锁的实现原理

互斥锁在出现锁的争夺时,未获得锁的线程会主动让出时间片,阻塞线程并睡眠,需要进行上下文切换,CPU会切换其它线程继续操作。
主动让出时间片并不总是代表效率高。让出时间片会导致操作系统切换到另一个线程,这种上下文切换通常需要 10 微秒左右,而且至少需要两次切换。如果等待时间很短,比如只有几个微秒,忙等就比线程睡眠更高效。

信号量的实现

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;
)

信号量和互斥锁类似,都是在获取锁失败后线程进入wait状态,CPU会切换时间片。
信号量在最终都是调用一个sem_wait方法,并原子性的判断信号量,如果对其-1后依然大于0,则直接返回,继续临界区操作,否则进入等待状态。

参考:https://bestswifter.com/ios-lock/

四、多线程中的常见术语

  • 条件(condition)
    一个用来同步资源访问的结构。线程等待某一条件来决定是否被允许继续运行,直到其他线程显式的给该条件发送信号。
  • 临界区(critical section)
    同一时间只能不被一个线程执行的代码。
  • 输入源(input source)
    一个线程的异步事件源。输入源可以是基于端口的或手工触发,并且必须被附加到某一个线程的run loop上面。
  • 可连接的线程(join thread)
    退出时资源不会被立即回收的线程。可连接的线程在资源被回收之前必须被显式脱离或由其他线程连接。可连接线程提供了一个返回值给连接它的线程。
  • 主线程(main thread)
    当创建进程时一起创建的特定类型的线程。当程序的主线程退出,则程序即退出。
  • 互斥锁(mutex)
    提供共享资源互斥访问的锁。一个互斥锁同一时间只能被一个线程拥有。试图获取一个已经被其他线程拥有的互斥锁,会把当前线程置于休眠状态知道该锁被其他线程释放并让当前线程获得。
  • 操作对象(operation object)
    NSOperation类的实例。操作对象封装了和某一任务相关的代码和数据到一个执行单元里面。
  • 操作队列(operation queue)
    NSOperationQueue类的实例。操作队列管理操作对象的执行。
  • 进程(process)
    应用或程序的运行时实例。一个进程拥有独立于分配给其他程序的的内存空间和系统资源(包括端口权限)。进程总是包含至少一个线程(即主线程)和任意数量的额外线程。
  • 递归锁(recursive lock)
    可以被同一线程多次锁住的锁。
  • 信号量(semaphore)
    一个受保护的变量,它限制共享资源的访问。互斥锁(mutexes)和条件(conditions)都是不同类型的信号量。
  • 任务(task)
    要执行的工作数量。尽管一些技术(最显著的是Carbon 多进程服务—Carbon Multiprocessing Services)使用该术语的意义有时不同,但是最通用的用法是表明需要执行的工作数量的抽象概念。
  • 线程(thread)
    进程里面的一个执行过程流。每个线程都有它自己的栈空间,但除此之外同一进程的其他线程共享内存。