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_retain
和dispatch_release
操作其ref count。
在Queue中使用Context保存信息
分别是dispatch_get_context
和dispatch_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_sync 、dispatch_sync_f |
异步添加 | dispatch_async 、dispatch_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_sync
、dispatch_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_apply
和dispatch_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_suspend
、dispatch_resume
这两个函数是通过增减queue的suspension ref count来实现suspend和resume的。
注意:在对一个queue调用dispatch_suspend的时候,该queue仍然会执行完当前正在run的task才会停下来。
使用信号量Dispatch Semaphores来控制临界资源的使用
比起传统的信号量,GCD提供的Dispatch Semaphores在临界资源available的情况调用并不会陷入到内核态去操作。只有在临界资源unavailable的时候才会陷入内核态去block thread,直到临界资源available。
操作步骤:
- 使用
dispatch_semaphore_create
函数创建信号量,指定临界资源数量 - 在每一个需要访问该临界资源的task里面,在访问操作代码前使用函数
dispatch_semaphore_wait
- 访问完之后使用
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_sync
、dispatch_group_async
、dispatch_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_sync
、dispatch_sync_f
追加新的task到当前queue中,这将会deadlock当前queue。 - 尽量不要使用资源锁,改为使用Serial Queues来同步访问资源
- 避免直接操作线程
其他:
关于dispath_once()