Dispatch Queues调度队列

前言-死锁案例html

// 在主线程中执行
dispatch_queue_t queueMain = dispatch_get_main_queue();
dispatch_sync(queueMain, ^{
        NSLog(@"+++++++");
});
NSLog(@"hahahaha");

案例分析:运行结果是程序阻塞在dispatch_sync()处。因为main线程执行到dispatch_sync()处,线程处于等待状态。将block任务块添加到主串行队列最后,block等待当前任务(即正在主线程中执行的任务)执行完毕,而当前任务由于阻塞没法结束,致使两边都在等待,因此出现死锁。缓存

 

1、简介安全

Dispatch queues是一种异步和并发执行任务的简单方式,将任务经过function或者block object的方式添加到dispatch queue。使用Dispatch queue仍是使用thread?Dispatch queue中只须要封装任务,再将任务加到合适的Disptch queue,由系统去管理线程。若是用thread,须要作的工做就多了。使用Dispatch queue实现同步,实现效果比加锁要有效。数据结构

由dispatch queue管理提交的任务,全部任务听从先进先出的规则,先添加到dispatch queue中的任务会最早开始执行。GCD提供了一些现成的dispatch queue,或者也能够本身定制dispatch queue。dispatch queue可分为3类:并发

一、Serial queues串行队列app

serial queues(private dispatch queue)按dispatch queue中的顺序一个时间执行一个任务,经常使用于同步的获取指定资源。你能够随意建立serial queues,多个serial queues之间是并行的。换句话说,建立了4个serial queues,单个queue中的任务串行,queue与queue中的任务是并行的。异步

二、Concurrent queues并发队列async

concurrent queues(global dispatch queue)并发的执行一个或多个任务,但也是按照先进先出的顺序开始启动任务。在某个时间点,可执行任务数量的上限取决于系统状态。在iOS5之后,能够经过函数dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)获取4中queue,也能够经过如下代码建立:ide

dispatch_queue_t queue = dispatch_queue_create("com.dispatch.concurrent", DISPATCH_QUEUE_CONCURRENT)

三、Main dispatch queue主队列函数

main dispatch queue(globally available serial queue)中的任务在主线程上串行执行。此queue和应用的run loop配合工做,主线程中在执行run loop的事件源后,插入queue任务。main dispatch queue为应用的主要同步queue。尽管不须要本身建立main dispatch queue,但使用时要注意。

 

2、与Dispatch Queues相关的技术

一、Dispatch groups

二、Dispatch semaphores

三、Dispatch sources(specific types of system events)

 

3、Block任务

一、block使用准则:

1)dispatch queue中的blocks异步地执行,最好在block只捕捉上下文中纯变量,不要试图捕捉结构体或者其它基于指针的变量,这些变量由调用上下文来分配和回收。缘由是在block执行中,捕获的变量内存可能已经被回收。固然有解决方案,好比为该对象分配内存,而后blcok指向并retain当前内存。

2)dispatch queue会copy添加到其中的block。等任务结束再释放block。换句话说,在添加到queue时不须要明确写copy代码。

3)尽管在执行小任务时,queues调度执行任务比原始的threads更有效,但调度和执行block仍然有开销。若是block中的任务轻,直接执行反而更高效。

4)不要缓存与基础线程相关的数据,不要试图从其它的block中获取数据。若是但愿同一个queue中的数据共享,使用dispatch queue的context指针来实现。

5)若是block内部建立了不少的OC对象,将block代码加到@autorelease块中,这样能更及时释放再也不使用的对象。尽管dispatch queue中带有自动释放池,但不保证会及时回收那些OC对象。

 

 4、建立和管理Dispatch Queue

一、获取Global Concurrent Dispatch Queues。

每一个应用程序有4个global concurrent dispatch queues,他们之间只有优先级的区别,包括的优先级有:DISPATCH_QUEUE_PRIORITY_HIGHDISPATCH_QUEUE_PRIORITY_DEFAULT、DISPATCH_QUEUE_PRIORITY_LOW、DISPATCH_QUEUE_PRIORITY_BACKGROUND。尽管dispatch queue存在引用计数,但不须要retain/release该队列,由于他们在应用中是全局可访问的,只须要经过方法获取该queue便可,代码以下:

dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

二、建立Serial Dispatch Queues

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

第一个参数是队列的名称,第二个暂时固定为NULL。

三、获取runtime的Common Queues

1)dispatch_get_current_queue()获取当前队列。若是在block内部调用,则返回的是block添加到的queue。若是是block外部调用,则返回当前queue。

2)dispatch_get_main_queue()获取主线程队列。

3)dispatch_get_global_queue()获取全局并发队列。

四、Dispatch Queue的内存管理

并发队列和主队列都不须要考虑内存管理的问题,只须要考虑本身定义的串行队列。经过dispatch_retain和dispatch_release方法增长和减小队列对象的引用计数器。

见到过在dealloc方法中,调用dispatch_release释放自定义的串行队列。

五、Dispatch Queue中数据存储-Context

dispatch queue能经过dispatch_set_context函数关联context data,经过dispatch_get_context方法来获取context data,context data存储了指向OC对象或者是数据结构的指针。context data须要手动分配和释放内存,可以使用queue的finalizer函数完成释放。代码以下:

dispatch_queue_t storeData()
{
    MyDataContext *contextData = (MyDataContext*) malloc(sizeof(MyDataContext));
    myInitializeDataContextFunction(contextData);
 
    // 建立串行队列,设置context data
    dispatch_queue_t serialQueue = dispatch_queue_create("com.example.CriticalTaskQueue", NULL);
    dispatch_set_context(serialQueue, contextData);

    // 回收context data
    dispatch_set_finalizer_f(serialQueue, &myFinalizerFunction);
 
    return serialQueue;
}

void myFinalizerFunction(void *context)
{
    MyDataContext* contextData = (MyDataContext*)context;
 
    // 清理内容结构
    myCleanUpDataContextFunction(contextData);
 
    // 释放
    free(contextData);
}

 

5、往Dispatch Queues中添加任务

一、添加单一任务

dispatch_sync()同步调用,会阻塞方法所在线程。

dispatch_async()异步调用,不会阻塞线程。

 

dispatch_barrier_async()。等待前面的任务所有结束,该task才能执行,而后后面的任务才能启动执行

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

dispatch_after(dispatch_time(DISPATCH_TIME_NOW,(int64_t)(2.0*NSEC_PER_SEC)),dispatch_get_main_queue(), ^{});// 将2秒后需执行的任务......

二、Completion Block

在dispatch queue中的某个任务执行结束后,通知作些后续操做。不一样于回调机制,dispatch queue采用completion block实现。具体操做是,在其任务最后添加将completion block提交到指定queue中。

//  myBlock和myQueue分别就是completion block和提交至的queue
void sum_async(int data01, int data02, dispatch_queue_t myQueue, void (^myBlock)(int))
{
   // Retain the queue provided by the user to make sure it does not disappear before the completion block can be called.
  dispatch_retain(myQueue);
 
   // Do the work on the default concurrent queue, then call the user-provided block with the results.        
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    int sumValue = data + len;
    dispatch_async(myQueue, ^{ myBlock(sumValue); 
  });
 
  // Release the user-provided queue when done
  dispatch_release(myQueue);
  });
}

三、并发执行循环迭代dispatch_apply()

在执行指定次数的循环中,且单个循环间彼此独立、执行的前后顺序可有可无。这时不该该再采用最初的for循环依次遍历,而应考虑经过concurrent dispatch queue提升性能。有个函数dispatch_apply(),能一次性的将全部循环提交给queue,当提交给concurrent queue时,就能同一时间执行多个循环。

固然也能够将任务提交到serial queue,但没什么优点和意义,不建议。

注意:跟普通的循环同样,dispatch_apply函数需等全部的循环运行结束后才会返回。由于会堵塞当前线程,在主线程中要慎用,避免执行全部循环阻碍主线程及时响应事件。若是循环要求比较长的处理时间,你应该在其它线程上调用dispatch_apply。

避免出现死锁场景:执行dispatch_apply所在线程的queue和入参中的queue是同一个,且queue是serial queue,那么线程执行到dispatch_apply函数时,需等待全部循环运行结束,而serial queue中的任务又只执行到dispatch_apply,得等当前任务执行完毕,双方互相等待彼此结束任务。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

// count是总共循环次数,i是当前循环数
dispatch_apply(count, queue, ^(size_t i) {
   printf("%u\n",i);
});

四、在主线程上执行任务

GCD提供的一个特殊的main dispatch queue,其中的任务在主线程中串行。可经过dispatch_get_main_queue函数可获取该queue,它由应用自动提供且自动回收,给主线程建立了一个run loop。

若是建立的不是cocoa应用程序,也不想明确建立run loop,那必须调用dispatch_main函数去明确执行main dispatch queue中的任务,不然,queue中的任务永远不会执行。

五、在任务中使用OC对象

GCD创建在cocoa内存管理的基础上,能够在任务中任意使用OC对象,每个dispatch queue都拥有本身的autorelease pool。但若是应用内存要求严格,且在任务中添加了不少的autoreleased对象,想要及时释放这些对象,建立本身的autorelease pool。

 

6、暂停和恢复Queue

经过调用dispatch_suspend函数暂时中止queue中的任务执行,调用dispatch_resume函数恢复执行。有一个suspension reference count,当暂停时该引用计数增长,当恢复时引用计数减小,为0时,queue是暂停状态。所以,必须平衡调用suspend和resume方法。

注意:这两个方法都是异步执行,在queue中任务之间生效,也就是说暂停不会让正在执行的任务停止,停止是下一个任务。

 

七、使用Dispatch Semaphore来有效使用有限资源

给某个有限资源,指定能同时访问的最大数量,能够经过dispatch semaphore实现。dispatch semaphore跟大多数semaphore同样,除了获取dispatch semaphore比系统的semaphore要快。这是由于GCD通常不访问内核,只有在资源不可用时,才会去内核调用,系统暂停线程以待semaphore发出信号。使用以下:

一、经过dispatch_semaphore_create函数建立semaphore,能够选择指定一个正整数来显示一次可访问上限。

二、在每一个任务,经过dispatch_semaphore_wait函数等待信号量。

三、当信号量发出信号,就能获取资源执行任务。

四、当使用资源结束后,经过dispatch_semaphore_signal函数释放信号并发出信号。

这些步骤如何工做可参考系统的file descriptor。每一个应用提供了有限的file descriptors,若是某个任务是处理大量的文件,你不想一次打开这么多文件以致于耗尽file descriptors,这时,就能够采用semaphore去限制使用file descriptors的数量,使用部分代码以下:

// 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);

 

8、Group Queue组任务队列

 dispatch group阻塞线程直到一个或多个任务执行结束。可用的场景是等待全部指定任务完成后,再执行某任务。另外一个相似于使用dispatch group的是thread join,二者实现方式不一样。

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);

 

9、Dispatch queues和线程安全

任什么时候候实现并发,都须要知道如下几点:

一、dispatch queue自己是线程安全的,换句话说,能够从任何线程提交任务到queue中,不须要考虑queue的锁和同步问题。

二、dispatch_sync调用所在的queueA,与入参queueB,若是queueA和queueB是同一个queue,那么会形成死锁的状况。使用dispatch_async函数就不会。

三、避免在任务中使用锁。若是是串行队列,若是获取不到锁,会阻塞整个队列中的任务执行。若是是并行队列,获取不到锁,会阻塞队列中其它任务的执行。因此须要同步执行代码,使用serial dispatch queue代替锁。

四、尽管能够获取任务所在基础线程信息,最好不要这样作。

 

10、该博客翻译自苹果官方文档

https://developer.apple.com/library/content/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html#//apple_ref/doc/uid/TP40008091-CH102-SW8

相关文章
相关标签/搜索