线程篇之GCD

本篇是是Objective-C 高级编程中多线程GCD部分 笔记.

dispatch_after

dispatch_after 不是在指定时间后执行处理,而只是在指定时间追加处理到 Dispatch Queue.

dispatch_group

经常有这种需求:在追加到 dispatch group 中的多个处理完全结束后想执行 结束处理. 如果 只是用一个 serial dispatch queue 时,只要将想要执行的处理全部追加到改 queue 中,并在最后追加 结束处理,即可实现. 但是在使用 concurrent dispatch queue 时,或同时使用多个 dispatch queue 时,就会变的很复杂.

在这种情况下使用 dispatch group ,例如下面的代码,在5个 block 追加到 queue 中,这些 block 如果全部执行完毕,就会执行main queue中的用于结束处理的 block

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"group task 1");
        
    });
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"group task 2");
        
    });
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"group task 3");
        
    });
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"group task 4");
        
    });
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"group task 5");
        
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"group task done");
    });

打印结果:

group task 1
group task 3
group task 5
group task 2
group task 4
group task done

dispatch group wait

在 dispatch group 中也可以使用 dispatch_group_wait 函数等待全部处理执行结束.

这里的"等待"意味着,一旦调用dispatch_group_wait函数,该函数就处于调用的状态而不返回,执行该函数的线程(当前线程)停止继续执行后面的语句. 经过dispatch_group_wait中指定的时间或者 group 中的任务全部执行结束后,该函数返回,继续执行后面的语句.

示例:

    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"group task 1");
        
    });
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"group task 2");
        
    });
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"group task 3");
        
    });
    //第二个参数是 dispatch_time_t 类型 ,这里使用 DISPATCH_TIME_FOREVER来表示如果group 中的 task没有执行 完毕,就永远等待.
    //dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1); ,一秒
    long result = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

    //dispatch_group_wait返回的值为0,意味着在指定的时间内 group 中的任务都已经执行完毕
    //如果不为0,则表示尚未执行完毕
    if (result == 0) {
        NSLog(@"group task done");
    }else{
        NSLog(@"group task not done");
    }

dispatch barrier async

对于文件的读操作,并没有资源的竞争,可以放在concurrent dispatch group 中,但是写操作就不行了, 会因为资源的竞争导致后续读写出错,还有其他的一些问题. 如果读写操作都有的话,可以使用dispatch_barrier_async函数.

dispatch_barrier_async函数会等待 追加到 并行队列上的任务 都处理完成之后,再将 指定的任务A 追加到 改 并行队列中,然后等 任务 A 处理完毕后, 该并行队列才恢复为一般的动作,追加到改队列中的任务又开始并行执行.

示例:

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    __block int count = 1;
    
    dispatch_async(queue, ^{
        NSLog(@"1 read count:%d",count);
    });
    dispatch_async(queue, ^{
        NSLog(@"2 read count:%d",count);
    });
    
    dispatch_barrier_async(queue, ^{
        
        count += 10;
       
    });
    
    dispatch_async(queue, ^{
        NSLog(@"3 read count:%d",count);
    });
    dispatch_async(queue, ^{
        NSLog(@"4 read count:%d",count);
    });

打印结果:

     2 read count:1
     1 read count:1
     3 read count:11
     4 read count:11

dispatch_sync函数

相对于dispatch_async 这种异步函数(不等待指定的任务完成,立即返回,即不停止当前线程), dispatch_sync这种同步函数,必须等待 指定的任务完成后才会返回,也就意味着会阻塞当前线程,如果使用不当甚至会导致死锁

死锁示例:

    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"dead lock");
    });

在上述代码中, dispatch_sync函数,将任务 A^{NSLog(@"dead lock");}加入到 main queue 中. 此时 dispatch_sync函数等待 main queue 中的 任务 A 执行完毕, 而 main queue 又在等待dispatch_sync函数返回, 这样就形成了一个死锁.如果使用dispatch_async 就可以避免

dispatch_apply函数

dispatch_apply函数是dispatch_sync函数和 dispatch group 关联的 API. 该函数按指定的次数 将指定的 任务追加到指定的 queue 中,并等待全部处理完成(意味着会阻塞当前线程)

示例:

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // 5:重复的次数
    dispatch_apply(5, queue, ^(size_t index) {
        NSLog(@"index: %zd",index);
    });
    NSLog(@"apply done");

打印结果:

    index: 1
    index: 0
    index: 2
    index: 3
    index: 4
    apply done

因为在 global queue 中执行处理, 任务并发执行,时间不确定.但是输出结果中的 apply done 必定在最后执行. 因为dispatch_apply函数会等待全部处理完成后返回,会阻塞当前线程,因此推荐在 子线程中使用

apply 可以快速的对数组进行处理,不必一个个编写 for 循环部分,例如:

  NSArray *nameArr = @[@"amos",@"andy",@"allen",@"sherry",@"hogan"];
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //在子线程中使用dispatch_apply
    dispatch_async(queue, ^{
        dispatch_apply([nameArr count], queue, ^(size_t index) {
            NSLog(@"name: %@",[nameArr objectAtIndex:index]);
        });
    });

dispatch_semaphore函数

在并行执行处理更新数据时,会产生数据不一致的情况,有时候应用还会异常崩溃, 虽然使用 serial queue 和 dispatch_barrier_async函数可以避免这类问题,但有必要进行更细粒度的 排他控制

考虑一种情况:不考虑顺序,将所有数据追加到NSMutableArray数组中

   NSMutableArray *numArr = [NSMutableArray array];
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    for (int i = 0; i < 10000; i++) {
        dispatch_async(queue, ^{
            
            [numArr addObject:[NSNumber numberWithInt:i]];
            
        });
    }

上述代码在执行后有可能会因为内存错误导致应用程序异常崩溃.此时应该使用 dispatch semaphore.

dispatch semaphore 是持有计数的信号,该计数 是多线程编程中的计数类型信号. 计数为0时等待,计数为1或大于1时,减去1 而不等待.

下面创建一个semaphore,计数的初始值为1:

    //创建一个semaphore,计数的初始值为1
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

dispatch_semaphore_wait函数等待 semaphore 的计数值计数为1或大于1

    //该函数等待指定的semaphore的计数大于或等于1
    //如果是,那么semaphore的计数-1,该函数返回,
    //否则 就像dispatch_group_wait的第二个参数一样,等待指定的时间
    long result = dispatch_semaphore_wait(semaphore, 1);
    
    if (result == 0) {
        NSLog(@"count of semaphore bigger than 1");
    }else{
        NSLog(@"not ready");
    }
    //给semaphore的计数加1(不加的话会一直等待,知道崩溃)
    dispatch_semaphore_signal(semaphore);

dispatch_semaphore_wait使用示例:

    NSMutableArray *numArr = [NSMutableArray array];
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    for (int i = 0; i < 10000; i++) {
        dispatch_async(queue, ^{
            dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
            
            //wait 执行之前semaphore计数值为1,执行后semaphore计数值-1,函数返回
            dispatch_semaphore_wait(semaphore, 1);
            //此时semaphore计数值为0,将一直处于等待状态,此时可访问numArr的线程将只有一个,所以可以安全更新
            [numArr addObject:[NSNumber numberWithInt:i]];

            //给semaphore的计数加1(不加的话会一直等待,知道崩溃)
            dispatch_semaphore_signal(semaphore);
        });
    }

上述代码将安全运行.

dispatch_once

该函数 保证 在应用程序执行中 只执行一次

示例:

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //your code
    });

dispatch I/O

在读取大文件时,如果将文件分成合适的大小并使用 global dispatch queue 并行读取的话, 应该会比一般的读取速度快不少. 现在输入/输出硬件已经可以做到一次使用多个线程更快地并行读取了,能实现这一功能的就是 dispatch I/O 和 dispatch data.

以下为苹果中使用Dispatch I/O和Dispatch Data的例子:

pipe_q = dispatch_queue_create("PipeQ",NULL);
pipe_channel = dispatch_io_create(DISPATCH_IO_STREAM,fd,pipe_q,^(int err){
   close(fd);
});

*out_fd = fdpair[i];

dispatch_io_set_low_water(pipe_channel,SIZE_MAX);

dispatch_io_read(pipe_channel,0,SIZE_MAX,pipe_q, ^(bool done,dispatch_data_t pipe data,int err){
   if(err == 0)
     {
       size_t len = dispatch_data_get_size(pipe data);
       if(len > 0)
       {
          const char *bytes = NULL;
          char *encoded;

          dispatch_data_t md = dispatch_data_create_map(pipe data,(const void **)&bytes,&len);
          asl_set((aslmsg)merged_msg,ASL_KEY_AUX_DATA,encoded);
          free(encoded);
          _asl_send_message(NULL,merged_msg,-1,NULL);
          asl_msg_release(merged_msg);
          dispatch_release(md);
       }
      }

      if(done)
      {
         dispatch_semaphore_signal(sem);
         dispatch_release(pipe_channel);
         dispatch_release(pipe_q);
      }
});

上述代码是 Apple System Log API 用的源代码.感兴趣的可以研究一下下.

dispatch source

GCD 中除了主要的 dispatch queue 之外,还有不太引人注目的 dispatch source. 它是 BSD 系内核惯有的 kqueue 包装

kqueue 是 XNU 内核中发生各种事件时,在应用程序编程方执行处理的技术.其 CPU 负荷非常小, 尽量不占用资源. kqueue 可以说是应用程序处理 XNU 内核中发生的各种事件的方法中最优秀的一种

dispatch source可以处理以下事件:

名称 内容
DISPATCH_SOURCE_TYPE_DATA_ADD 变量增加
DISPATCH_SOURCE_TYPE_DATA_�OR 变量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 可写入文件映像

在 Core Foundation 框架的用于异步网络的 API CFSocket 中也使用了这些. 最后展示一个使用了DISPATCH_SOURCE_TYPE_TIMER定时器的例子.在网络编程的通信超时等情况下可以使用该例:

    //获取 global queue
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //创建 timer
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    //设置定时器,每秒执行一次, 可延迟1秒
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 1 * NSEC_PER_SEC);
    //设置定时器执行的事件
    dispatch_source_set_event_handler(timer, ^{
        
    });
    //指定取消 source 时的处理
    dispatch_source_set_cancel_handler(timer, ^{
        
    });
    
    dispatch_resume(timer);

如果细心你应该会发现,这里的 dispatch source 有取消的操作. 而 dispatch queue 是没有 "取消"这一概念的. 一旦将任务追加到 dispatch queue中,就没有办法可以将该任务去除,也没有方法可以在执行中取消该处理. 编程人员可以使用 NSOperationQueue 等方法实现queue 任务的取消.

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

推荐阅读更多精彩内容

  • 多线程概念 线程线程指的是:1个CPU执行的CPU命令列为一条无分叉路径 多线程这种无分叉路径不止一条,存在多条即...
    我系哆啦阅读 528评论 0 5
  • 最近颇花了一番功夫把多线程GCD人的一些用法总结出来,一来帮自己巩固一下知识、二来希望能帮到对这一块还迷茫...
    人活一世阅读 251评论 1 1
  • Dispatch Queues dispatch queues是执行任务的强大工具,允许你同步或异步地执行任意代码...
    YangPu阅读 593评论 0 4
  • 一、多线程简介: 所谓多线程是指一个 进程 -- process(可以理解为系统中正在运行的一个应用程序)中可以开...
    寻形觅影阅读 953评论 0 6
  • 今天第一本书《趣学算法》正式出版,三年写作,八月出版,太多的艰辛和不易。 常常坐在电脑前,就忘记喝水,忘记去卫生间...
    rainchxy阅读 247评论 0 1