Concurrency Programming Guide 4 Dispatch Queues

Dispatch Queues

type of Dispatch Queues

类型 描述 限制
Serial 也叫private dispatch queue,串行执行queue上的每一个task。 每一个task都在独立的线程里运行,由dispatch queue统一管理。经常用来控制对特定资源的访问(如临界资源)。 木有限制,你可以创建任意多个的serial dispatch queue,他们之间是并行的。
Concurrent 也叫global diapatch queue,并行执行queue里面的每一个task。每一个task都在独立的线程里运行,由dispatch queue统一管理。并行的数量是动态地,由system当前的资源状态决定。 concurrent dispatch queue有四个,由系统提供,根据优先级别区分为default、low、high、background。
Main dispatch queue 这个diapatch queue在main thread里串行执行task。所以这个queue是关联main thread的run loop的,用来执行dispatch source相关的任务。因为该queue在main thread执行task,所以常用作各任务的同步点 全局唯一的特殊serial dispatch queue

注意

尽量确保queue里面不要有两个以上的task被lock了,这将会严重影响线程并行效率。

其他注意事项:

  • dispatch queue之间都是并行执行task的
  • 任何时候,线程并发数量都是由系统决定的
  • queue优先级别将会影响queue中task的执行优先度
  • 加入到queue的task都应该是初始化完可以马上运行的
  • serial/private dispatch queue是基于ref counted的

相关技术

类型 描述
Dispatch groups Dispatch group可以同步/异步地监视一个block集合是否运行完,可以设置一个completion block。
Dispatch semaphores 和一般的信号量相近,但只有在该信号量为零或者负数的时候才会陷入内核态。
Dispatch sources 监听sys event事件

使用block创建task

注意:

  • 尽量不要capture太大的数据到block内部
  • Dispatch queue会copy block
  • 使用性能工具测试block实现的task工作量是否太小
  • 使用dispatch context来保存数据
  • 使用@autorelease block来管理内存,如果你创建了太多object-c对象实例

管理/创建Dispatch Queues

Global Concurrent Dispatch Queues

影响线程并发数量的因素:

  • 可使用的CPU内核数
  • 当前其他进程的工作量
  • application其他serial dispatch queues的内需要执行的任务数量和queue优先级别

创建:

dispatch_queue_t = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

四个queue优先级别

  • DISPATCH_QUEUE_PRIORITY_HIGH
  • DISPATCH_QUEUE_PRIORITY_LOW
  • DISPATCH_QUEUE_PRIORITY_DEFAULT
  • DISPATCH_QUEUE_PRIORITY_BACKGROUND

虽然所有dispatch queue都是ref counted的,但是不需要对Global Concurrent Dispatch Queue作retain和release操作,因为它是全局变量。用的时候直接调dispatch_get_global_queue方法取得ref就可以。

创建Serial Dispatch Queues

需要自己创建:

dispatch_queue_t queue;
queue = dispathc_queue_create(@"com.example.MyQueue",NULL);

*请尽量使用serial dispatch queue来代替资源锁机制。

Runtime中获取线程的函数

  • dispatch_get_current_queue
  • dispatch_get_main_queue
  • dispatch_get_global_queue

Dispatch Queue的内存管理

由于Dispatch Queues和其他Dispatch对象都是ref counted的。除了全局变量Global Concurrent Dispatch Queue以外,其他均需要使用dispatch_retaindispatch_release操作其ref count。

在Queue中使用Context保存信息

分别是dispatch_get_contextdispatch_set_context

Queue Clean Up Function

使用dispatch_get_finalizer_f设置Clean Up函数:

 void myFinalizerFunction(void *context)
  {
      MyDataContext* theData = (MyDataContext*)context;

      // Clean up the contents of the structure
      myCleanUpDataContextFunction(theData);
      // Now release the structure itself.
      free(theData);
  }


  dispatch_queue_t createMyQueue()
  {
      MyDataContext*  data = (MyDataContext*) malloc(sizeof(MyDataContext));
      myInitializeDataContextFunction(data);
      // Create the queue and set the context data.
      dispatch_queue_t serialQueue =
  dispatch_queue_create("com.example.CriticalTaskQueue", NULL);


      if (serialQueue)
      {
          dispatch_set_context(serialQueue, data);
          dispatch_set_finalizer_f(serialQueue, &myFinalizerFunction);
      }
      return serialQueue;
  }

添加task到queue

类型 函数
同步添加 dispatch_syncdispatch_sync_f
异步添加 dispatch_asyncdispatch_async_f

栗子

dispatch_queue_t myCustomQueue;
myCustomQueue = dispatch_queue_create("com.example.MyCustomQueue", NULL);


dispatch_async(myCustomQueue, ^{
    printf("Do some work here.\n");
});


printf("The first block may or may not have run.\n");


dispatch_sync(myCustomQueue, ^{
    printf("Do some more work here.\n");
});


printf("Both blocks have completed.\n");

注意:不管是Serial还是Concurrent Queue,都不能在task中调用dispatch_syncdispatch_sync_f追加新的task到当前queue中,这将会deadlock当前queue。

设置一个task的Completion Block

栗子


void average_async(int *data, size_t len, dispatch_queue_t queue, void (^block)(int)) { // Retain the queue provided by the user to make // sure it does not disappear before the completion // block can be called. dispatch_retain(queue); // Do the work on the default concurrent queue and then // call the user-provided block with the results. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ int avg = average(data, len); dispatch_async(queue, ^{ block(avg);}); // Release the user-provided queue when done dispatch_release(queue); }); }

并行迭代

使用dispatch_applydispatch_apply_f

注意:这两个函数仍然会block当前线程,在main queue中使用时请注意。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_apply(count, queue, ^(size_t i) {
   printf("%u\n",i);
});

在main thread中添加task

  • 使用dispatch_get_main_queue获取main queue
  • 使用dispatch_async添加task来避免打断当前task的执行

Suspending和Resume Queues

函数dispatch_suspenddispatch_resume

这两个函数是通过增减queue的suspension ref count来实现suspend和resume的。

注意:在对一个queue调用dispatch_suspend的时候,该queue仍然会执行完当前正在run的task才会停下来。

使用信号量Dispatch Semaphores来控制临界资源的使用

比起传统的信号量,GCD提供的Dispatch Semaphores在临界资源available的情况调用并不会陷入到内核态去操作。只有在临界资源unavailable的时候才会陷入内核态去block thread,直到临界资源available。

操作步骤:

  1. 使用dispatch_semaphore_create函数创建信号量,指定临界资源数量
  2. 在每一个需要访问该临界资源的task里面,在访问操作代码前使用函数dispatch_semaphore_wait
  3. 访问完之后使用dispatch_semaphore_singal

栗子

// Create the semaphore, specifying the initial pool size
dispatch_semaphore_t fd_sema = dispatch_semaphore_create(getdtablesize() / 2);

// Wait for a free file descriptor
dispatch_semaphore_wait(fd_sema, DISPATCH_TIME_FOREVER);

fd = open("/etc/services", O_RDONLY);
// Release the file descriptor when done
close(fd);

dispatch_semaphore_signal(fd_sema);

Dispatch Groups

dispatch groups是可以让你在一个或多个tasks完成之后执行指定代码的机制

  • 可以用来监听task是否完成,在完成之后执行指定代码来处理执行结果
  • 可以用来实现类似Operation Queue的dependency机制,等待一系列children task完成之后再把parent task加入到queue中

dispatch_group_syncdispatch_group_asyncdispatch_group_wait

栗子

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_group_t group = dispatch_group_create();
// Add a task to the group

dispatch_group_async(group, queue, ^{
   // Some asynchronous work
});

// Do some other work while the tasks execute.
// When you cannot make any more forward progress,
// wait on the group to block the current thread.

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

// Release the group when it is no longer needed.
dispatch_release(group);

Dispatch Queues线程安全

  • Dispatch Queues是线程安全的
  • 不管是Serial还是Concurrent Queue,都不能在task中调用dispatch_syncdispatch_sync_f追加新的task到当前queue中,这将会deadlock当前queue。
  • 尽量不要使用资源锁,改为使用Serial Queues来同步访问资源
  • 避免直接操作线程

其他:
关于dispath_once()

  1. block介绍(三)揭开神秘面纱(上)1
  2. block介绍(三)揭开神秘面纱(下)2

标签:ios, object-c, gcd, dispatch-queues, ios并发编程, ios多任务多线程