多线程之GCD

简介

GCD:(Grand Central Dispatch)GCD是基于C语言的。
GCD本身是苹果公司为多核的并行运算提出的解决方案,它完全由系统管理线程,我们不需要编写线程代码,只需要在适当的调度队列中(dispatch queue)实现要执行的任务就好。GCD会负责创建线程和调度你的任务,系统直接提供线程管理。
GCD能推迟昂贵的计算任务,并在后台运行,从而可以改善工程的性能。
GCD能提供易于我们使用的并发模型从而帮我们避开并发陷阱。

调度队列(dispath queue)

queue 是GCD的一个重要概念,我们可以这样理解:我们要执行一个长期任务,可以将这个任务分成多个工作单元,并将这些工作单元添加到dispath queue 中,系统则会为我们管理这些工作单元,,为我们在多个线程上执行工作单元,我们不需要直接启动和管理后台线程。
GCD的dispach queue 是严格按照先进先出的的原则工作的,添加到dispath queue的工作单元将始终按照加入dispath queue的顺序启动。

一、并行队列(concurrent dispath queue)
  • 并发是可以同时执行多个任务,实际上也是按照先进先出(FIFO)的原则执行的,并发会在上一个任务即将执行完之前就出列下一个任务并执行。
  • 并发的任务数量,会根据应用和系统动态变化,例如:可用的核数量、其他进程正在执行的工作量、其他串行的工作量等等。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
DISPATCH_QUEUE_PRIORITY_HIGH 2      优先级最高
DISPATCH_QUEUE_PRIORITY_DEFAULT 0   优先级默认
DISPATCH_QUEUE_PRIORITY_LOW (-2)    优先级最低

二、串行队列(serial dispatch queue)

  • 应用任务按顺序执行,串行queue每次只执行一个任务,可替代锁,和锁不一样的是,串行queue确保任务按可预测的顺序执行。并且不容易产生死锁。
dispatch_queue_t queue = dispatch_queue_create("cn.test.queue", NULL);
第一个参数是queue的名字
第二个参数是一组queue的属性

三、同步&异步

  • 同步:函数执行玩这个任务才能执行下一个任务。对于GCD而言:同步函数执行一个任务,直到执行完成它的预定任务后才返回。
  • 异步:函数不会等到任务完成,它会立即执行下一个任务,同时当前任务会继续在后台执行,对于GCD而言:与同步相反,它会立即返回,而任务继续执行,它并不会等待任务完成再返回。
    NSLog(@"当前线程:%@", [NSThread currentThread]);
    //创建一个并发线程
    dispatch_queue_t queue;
    queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
    //创建一个异步任务
    dispatch_async(queue, ^{
        NSLog(@"异步任务,当前线程:%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"同步任务,当前线程:%@",[NSThread currentThread]);
    });
打印消息:
当前线程:{number = 1, name = main}
异步任务,当前线程:{number = 2, name = (null)}
同步任务,当前线程:{number = 1, name = main}

四、循环迭代(dispatch_apply)

  • 如果每次迭代与其他迭代独立无关,,而且循环迭代执行顺序也无关紧要的话,我们可以尝试使用dispath_applydispatch_apply_f代替for循环。
    这两个函数可以同时并发多个循环迭代,这样可以大大提高效率。
  • dispatch_applydispatch_apply_f时你可以指定串行或并发 queue。并发queue允许同时执行多个循环迭代,而串行queue就没太大必要使用了。
    queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
    //你传递的block必须包含一个size_t类型的参数,用来标识当前循环迭代。第一次迭代这个参数值为0,最后一次值为count - 1
    size_t count = 8;
    dispatch_apply(count, queue, ^(size_t t) {
        NSLog(@"%zu",t);
    });
打印结果:
2016-07-13 11:10:33.423 GCDTest[1867:83704] 0
2016-07-13 11:10:33.423 GCDTest[1867:83703] 2
2016-07-13 11:10:33.423 GCDTest[1867:83662] 1
2016-07-13 11:10:33.423 GCDTest[1867:83714] 3
2016-07-13 11:10:33.423 GCDTest[1867:83704] 4
2016-07-13 11:10:33.424 GCDTest[1867:83703] 5
2016-07-13 11:10:33.424 GCDTest[1867:83662] 6
2016-07-13 11:10:33.424 GCDTest[1867:83714] 7

由此可见,这些迭代是并发执行的,并且顺序不固定,

五、暂停和继续queue

  • 我们可以使用dispatch_suspend函数暂停一个queue以阻止它执行block对象;使用dispatch_resume函数继续dispatch queue。
  • 挂起一个queue不会导致正在执行的block停止,只会导致不再继续执行还未执行的任务。dispatch_resume会唤醒已挂起的dispatch queue。
  • 调用dispatch_suspend会增加queue的引用计数,调用dispatch_resume则减少queue的引用计数。
  • 当引用计数大于0时,queue就保持挂起状态。因此你必须对应地调用suspend和resume函数(必须成对出现)。
  • 挂起和继续是异步的,而且只在执行block之间(比如在执行一个新的block之前或之后)生效。
    NSLog(@"当前线程:%@", [NSThread currentThread]);
    //创建一个串发线程
    dispatch_queue_t queue;
    queue = dispatch_queue_create("cn.test.queue", NULL);
    dispatch_async(queue, ^{
        NSLog(@"异步任务,当前线程:%@",[NSThread currentThread]);
    });
    //挂起queue
    dispatch_suspend(queue);
    dispatch_sync(queue, ^{
        NSLog(@"同步任务,当前线程:%@",[NSThread currentThread]);
    });
    //结束挂起
    dispatch_resume(queue);
打印结果
当前线程:{number = 1, name = main}

六、常用

//  后台执行:
 dispatch_async(dispatch_get_global_queue(0, 0), ^{
      // something
 });
 // 主线程执行:
 dispatch_async(dispatch_get_main_queue(), ^{
      // something
 });

 // 一次性执行:
 static dispatch_once_t onceToken;
 dispatch_once(&onceToken, ^{
     // code to be executed once
 });

 // 延迟2秒执行:
 double delayInSeconds = 2.0;
 dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
 dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
     // code to be executed on the main queue after delay
 });

 // 自定义dispatch_queue_t
 dispatch_queue_t urls_queue = dispatch_queue_create("blog.devtang.com", NULL);
 dispatch_async(urls_queue, ^{  
   // your code 
 });
// 销毁队列 
 dispatch_release(urls_queue);

 // 合并汇总结果
 dispatch_group_t group = dispatch_group_create();
 dispatch_group_async(group, dispatch_get_global_queue(0,0), ^{
      // 并行执行的线程一
 });
 dispatch_group_async(group, dispatch_get_global_queue(0,0), ^{
      // 并行执行的线程二
 });
 dispatch_group_notify(group, dispatch_get_global_queue(0,0), ^{
      // 汇总结果
 });
dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t queue);
将需要执行的任务对象指定到不同的队列中去处理,比如说有两个队列dispatchA和dispatchB,这时把dispatchA指派到dispatchB:

七、一些概念

  • 串行&并行: 任务串行执行就是每次只有一个任务被执行,任务并发执行就是在同一时间可以有多个任务被执行。
  • 临界区: 就是一段代码不能被并发执行,也就是,两个线程不能同时执行这段代码。这很常见,因为代码去操作一个共享资源,例如一个变量若能被并发进程访问,那么它的值将不再可信。
  • 竞态条件: 这种状况是指基于特定序列或时机的事件的软件系统以不受控制的方式运行的行为,例如程序的并发任务执行的确切顺序。竞态条件可导致无法预测的行为,而不能通过代码检查立即发现。
  • 死锁:所谓的死锁是指两个线程它们都卡住了,并等待对方完成或执行其它操作。第一个不能完成是因为它在等待第二个的完成。但第二个也不能完成,因为它在等待第一个的完成。
  • 线程安全: 线程安全是代码能在多线程或并发任务中被安全的调用,而不会导致任何问题(数据损坏,崩溃,等)。线程不安全的代码在某个时刻只能在一个环境中运行。一个线程安全代码的例子是 NSDictionary 。你可以在同一时间在多个线程中使用它而不会有问题。另一方面,NSMutableDictionary 就不是线程安全的,应该保证一次只能有一个线程访问它。

你可能感兴趣的