iOS多线程编程之GCD详解(二)完结

前言

上一篇详细介绍了介绍了GCD中的常用API,
iOS多线程编程之GCD详解(一)
考虑到篇幅问题,这里继续介绍另外的两个API。

1.Dispatch Semaphore 信号量

dispatch_semaphore_t 信号量本质上是一种锁。
关于iOS中各种锁和性能比较可以看下yykit作者的这篇博文,戳这里
不再安全的 OSSpinLock

下面我们看下信号量的使用:
dispatch_semaphore_t 的作用之一解决资源抢夺问题
之前提过,对于数据存储类似数据库,非原子性可变字典和可变数组等多线程下不安全的操作,可以使用同步队列保证线程安全,那么在并发队列中,可以使用信号量来解决资源抢夺问题

  //全局队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //创建一个信号量,初始值为1
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1) ;
    //创建可变数组
    NSMutableArray *array = [[NSMutableArray alloc] init];
    for(int i = 0; i< 1000; ++i) {
        dispatch_async(queue, ^{
            
            //这里会一直等待,直到信号量大于等于1
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER) ;
            
            //执行到这里,消费一个信号量
            NSLog(@"%@",[NSThread currentThread]);
            [array addObject:[NSNumber numberWithInt:i]];
            
            //这里增加一个信号量
            dispatch_semaphore_signal(semaphore);
        });
    }

代码解读一下:
dispatch_semaphore_create(1) 创建了值为1信号量
dispatch_semaphore_wait ,如果信号量的值大于等于1,那么,信号量值减1,然后向下执行,如果信号量值为0,一直等待。直到大于等于1的时候,率先进入等待状态的异步队列率先执行
dispatch_semaphore_signal 信号量值加1

形象比喻一下:
一群人排队去银行办业务,银行初始只有一个窗口,第一个人办业务的时候,可用窗口就变成0个了,这个人办完业务,可用窗口加1,就变成1个了。

实际这种效果和加锁的本质一致。
dispatch_semaphore_t 的另外一个作用就是可以控制线程并发数量,之前我们提过,iOS7之后系统自动开辟的线程数量可以多达60-70,而GCD中并没有提供控制线程数量的API,NSOperation中可以设置最大线程数。

下面我们使用信号量来实现一下线程数量控制:

    //线程并发数限制
    static dispatch_semaphore_t limitSemaphore;
    //控制专用队列
    static dispatch_queue_t serialQueue;
    
    //单例创建
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
      //设置最大线程并发数为5
        limitCount = dispatch_semaphore_create(5);
        serialQueue = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
    });
    
    dispatch_async(serialQueue, ^{
        //信号量>=1继续执行,否则等待
        dispatch_semaphore_wait(limitSemaphore, DISPATCH_TIME_FOREVER);
        dispatch_async(queue, ^{
            //这里执行一些任务
            NSLog(@"%@",[NSThread currentThread]);
            //在该工作线程执行完成后释放信号量
            dispatch_semaphore_signal(limitSemaphore);
        });
    });

2.dispatch source

dispatch source 是一组不常用的GCD API。是BSD系内核惯有功能kqueue的包装。kqueue的介绍可以看下这个kqueue wikipedia
简单来说,dispatch source是一个监视某些类型事件的对象。它支持所有kqueue所支持的事件以及mach(mach介绍可以看这里mach wikipedia)端口、内建计时器支持和用户事件,CPU负荷占用小,资源占用小。

dispatch source联结
联结的流程:在任一线程上调用dispatch_source_merge_data 这个函数后,会执行 Dispatch Source 事先定义好的句柄(可以简单理解句柄就是block )(是不是有点通知,回调的味道哈)
下面直接上代码:

//全局队列
 dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//创建source
    dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
//定义source的句柄
    dispatch_source_set_event_handler(source, ^{
//调用一次dispatch_source_merge_data会调用这个句柄
        NSLog(@"%lu",dispatch_source_get_data(source));
    });
    //默认source是suspend的,需要resume生效
    dispatch_resume(source);
    
//遍历10次
    dispatch_apply(10, globalQueue, ^(size_t index) {
        // merge data
        dispatch_source_merge_data(source, 1);
    });

这段程序简单逻辑:调用dispatch_source_merge_data 会触发实现定义好的事件

dispatch_source_set_event_handler(source, ^{
//调用一次dispatch_source_merge_data会调用这个句柄
        NSLog(@"%lu",dispatch_source_get_data(source));
    });

dispatch_source_create 函数参数DISPATCH_SOURCE_TYPE_DATA_ADD 累加
当注册系统事件的时候,有时候系统还没来得及通知应用程序,这个时候,系统会累计传递过来的值
DISPATCH_SOURCE_TYPE_DATA_OR 逻辑或处理累计传递过来的值
其他:
DISPATCH_SOURCE_TYPE_MACH_SEND MACH端口发送
DISPATCH_SOURCE_TYPE_MACH_RECV MACH端口接收
DISPATCH_SOURCE_TYPE_PROC 监测进程相关事件
DISPATCH_SOURCE_TYPE_READ 可读取文件映像
DISPATCH_SOURCE_TYPE_SIGNAL 接收信号
DISPATCH_SOURCE_TYPE_TIMER 定时器
DISPATCH_SOURCE_TYPE_VNODE 文件系统变更
DISPATCH_SOURCE_TYPE_WRITE 可写入文件映像

注册事件处理程序通知,如果系统没来的及通知应用程序时候事件发生多次,这些事件会合并为一个事件(是不是类似于TCP协议中的nagle算法)。iOS开发者通常不会用到这种功能。但对于底层,这种处理方式会很高效.

简单流程总结:创建一个源,自定义累计方式,可以是and也可以是Or,自定义源也需要一个队列用来处理响应块,可以是主队列,也可以是并发队列。

在同一时间,只有一个响应块被分派。处理方法没执行完毕,另一个事件发生,事件以指定方式(ADD或者OR)进行累积。通过合并,保证了在高负载下稳定执行。
累计值通过 dispatch_source_get_data 获取。每次响应执行事件,这个值会被重置
dispatch_source_merge_data 发送一个事件
默认创建出来的source是挂起状态的,需要调用dispatch_resume 才可生效

除了高效的自定义一个source处理自定事件之外,我们也可以使用dispatch_source 来定义一个定时器,iOS开发中常用的定时器有NSTimerCADisplayLink 两种
NSTimer 受到runloop的状态影响精度
CADisplayLink 则和屏幕刷新度帧数一致
dispatch_source 作为定时器精度很高,是系统级别的源

demo如下:

dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//定时器作为属性创建
     self.timerSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    //开始时间
    dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, 3.0 * NSEC_PER_SEC);
    //间隔时间
    uint64_t interval = 2.0 * NSEC_PER_SEC;
  //设置时间
    dispatch_source_set_timer( self.timerSource start, interval, 0);

    //设置回调
    dispatch_source_set_event_handler( self.timerSource, ^{
        //处理事件
    });

    //启动定时器
    dispatch_resume( self.timerSource);

总结一下:
本文主要介绍了
dispatch_semaphore_t ,本质是一种底层锁,性能较高,可以用来解决多线程资源竞争,控制线程并发数。
dispatch source 最大的优势是联结,通过合并事件的方式,高效的处理事件分派,可以自定义source用来处理高负载应用场景响应。dispatch source 可以作为高精度,系统源层级的定时器,在需要高精度应用场景下可以选用这种更加接近底层的定时器。

写了两篇博客来详解了下GCD,主要是对自我基础的一个总结。之前已经有很多写的很好的GCD文章。关于信号量和dispatch_source还有兴趣深入了解的可以阅读下官方文档。
推荐下阅读猿神的博客
Parse源码浅析系列(一)---Parse的底层多线程处理思路:GCD高级用法

参考书籍:
Objective-C高级编程

前篇:
iOS多线程编程之GCD详解(一)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容