调度队列(多线程笔记二)
调用队列的类型
| 类型 | 介绍 |
|---|---|
| 串行 | 串行队列一次只执行一个任务,根据任务添加到队列的顺序进行执行。 |
| 并行 | 并行队列可同时执行多个任务,在任务添加到队列的时候就开始执行。 |
| 主队列 | 主队列是在主线程上执行的全局串行队列。 |
在两个不同线程中访问同一份数据的时候,大多数情况下我们都是使用lock来保证两个线程不会同时修改这数据。但是如果使用调度队列的话,可以将任务添加到一个串行队列中来保证一次只有一个任务在执行数据访问。相比于lock,调度队列的消耗会低得多,并且也只会在必要的时候会调用到内核。(这里有点需要注意,在串行队列中添加两个任务,这两个任务并不会并发执行,跟在两个线程中分别执行任务是不同的,为什么还能拿来比较?这是因为,如果两个不同的线程在同一时间都获取同一个lock的话,那么其并发执行的标识就会丢失,也就是说,不会执行并发操作。)
一些调度队列的要点:
- 调度队列执行任务的并发性是相对其他队列而言的
- 一次性执行的任务数量会系统进行决定。
- 私有的调度队列是引用计数对象。
相关属性
| 名称 | 介绍 |
|---|---|
| Dispatch groups | 通常用于执行多个block对象 |
| Dispatch semaphores | 在semaphore为0时,会阻塞线程 |
| Dispatch sources | 生成系统事件相关的通知。当某个事件发生时,dispatch source会将任务提交到指定的队列中异步执行 |
使用Blocks实现任务
使用要点:
- 在调度队列中使用block的话,可以在block中安全地使用父类函数方法
- 调度队列在使用block的时候会copy之,并在执行完毕之后将其释放。
- 如果过多使用OC对象的话,应该将block代码添加到
@autorelease中来进行对象的存储管理。虽然GCD有自己的释放池,但是保证度并不高。 - 不要缓存数据并在另一个block中共享。如果需要在同个队列中共享数据,可以使用上下文指针来保存该数据对象。
调度队列的创建和管理
获取全局并发调度队列
iOS给我们提供了四个优先级不同的并发队列:DISPATCH_QUEUE_PRIORITY_DEFAULT, DISPATCH_QUEUE_PRIORITY_HIGH, DISPATCH_QUEUE_PRIORITY_LOW和DISPATCH_QUEUE_PRIORITY_BACKGROUND. 我们只需要通过dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)这样的方法来进行获取即可(第二个参数仅用于之后的扩展,在此传入0即可)。
串行队列的创建
dispatch_queue_t queue;
queue = dispatch_queue_create("com.example.MyQueue", NULL);
在Runtime中获取队列
dispatch_get_current_queue在block对象中调用的话会返回block所在的队列,在block外调用的话会返回app的默认的并发队列dispatch_get_main_queue获取app主线程相关的串行调用队列。dispatch_get_global_queue获取全局队列
调度队列的内存管理
调度队列相关的对象都是引用计数类型的。使用dispatch_retain和dispatch_release来增添或者减少计数。当计数为0的时候,会被系统回收释放。
清除函数
在创建串行调度队列之后,在队列释放的时候,可以选择性地提供一个函数,来清除其他参数.
void myFinalizerFunction(void *context) {
MyDataContext *theDate = (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);
dispatch_set_context(serialQueue, data);
dispatch_set_finalizer_f(serialQueue, &myFinalizerFunction);
return serialQueue;
}
将操作添加到队列中
添加单个任务
使用dispatch_async和dispatch_async_f来添加并发执行的任务。使用dispatch_sync和dispatch_sync_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 my or may not have run.\n");
dispatch_sync(myCustomQueue, ^{
printf("Do some more work here.\n");
});
printf("Both blocks have completed.\n");
队列的挂起和继续
使用dispatch_suspend和dispatch_resume,调用dispatch_suspend的话会增加计数,dispatch_resume则会减少。
需要注意的是,GCD队列的挂起和继续的调用时异步执行的,也就是说就算执行了挂起操作,队列的执行也不会马上停下来。并且挂起的不能是一个已经在执行中的队列。
Dispatch Semaphore
- 使用
dispatch_semaphore_create创建semaphore时需要指定一个正整数来表明数量 - 在任务中调用
dispatch_semaphore_wait来进行等待 - 完成任务后,调用
dispatch_semaphore_signal函数来进行释放
// Create the semaphore, sepcifying the initial pool size
dispatch_semaphore_t fd_sema = dispatch_semaphore_create(getdtablesize() / 2);
// Wait for a free file desc
dispatch_semaphore_wait(fd_sema, DISPATCH_TIME_FOREVER);
fd = open("/etc/services", O_RDONLY);
// Release when done
close(fd);
dispatch_semaphore_signal(fd_sema);