Dispatch Queues调度队列

前言-死锁案例

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

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

一、简介

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类:

1、Serial queues串行队列

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

2、Concurrent queues并发队列

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

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

3、Main dispatch queue主队列

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

二、与Dispatch Queues相关的技术

1、Dispatch groups

2、Dispatch semaphores

3、Dispatch sources(specific types of system events)

三、Block任务

1、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对象。

四、创建和管理Dispatch Queue

1、获取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);

2、创建Serial Dispatch Queues

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

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

3、获取runtime的Common Queues

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

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

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

4、Dispatch Queue的内存管理

并发队列和主队列都不需要考虑内存管理的问题,只需要考虑自己定义的串行队列。通过dispatch_retain和dispatch_release方法增加和减少队列对象的引用计数器。

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

5、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);}

五、往Dispatch Queues中添加任务

1、添加单一任务

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秒后需执行的任务......

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

3、并发执行循环迭代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);
});

4、在主线程上执行任务

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

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

5、在任务中使用OC对象

GCD建立在cocoa内存管理的基础上,可以在任务中任意使用OC对象,每一个dispatch queue都拥有自己的autorelease pool。但如果应用内存要求严格,且在任务中添加了很多的autoreleased对象,想要及时释放这些对象,创建自己的autorelease pool。

六、暂停和恢复Queue

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

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

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

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

1、通过dispatch_semaphore_create函数创建semaphore,可以选择指定一个正整数来显示一次可访问上限。

2、在每个任务,通过dispatch_semaphore_wait函数等待信号量。

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

4、当使用资源结束后,通过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);

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

九、Dispatch queues和线程安全

任何时候实现并发,都需要知道以下几点:

1、dispatch queue本身是线程安全的,换句话说,可以从任何线程提交任务到queue中,不需要考虑queue的锁和同步问题。

2、dispatch_sync调用所在的queueA,与入参queueB,如果queueA和queueB是同一个queue,那么会造成死锁的情况。使用dispatch_async函数就不会。

3、避免在任务中使用锁。如果是串行队列,如果获取不到锁,会阻塞整个队列中的任务执行。如果是并行队列,获取不到锁,会阻塞队列中其它任务的执行。所以需要同步执行代码,使用serial dispatch queue代替锁。

4、尽管可以获取任务所在基础线程信息,最好不要这样做。

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

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

时间: 2024-10-11 20:33:06

Dispatch Queues调度队列的相关文章

详解IOS开发应用之并发Dispatch Queues (2011)

详解IOS开发应用之并发Dispatch Queues是本文哟啊介绍的内容,我们几乎可以调度队列去完成所有用线程来完成的任务.调度队列相对于线程代码更简单,易于使用,更高效.下面讲主要简述调度队列,在应用中如何使用调度队列去执行任务. 1.关于调度队列 所有的调度队列都是先进先出队列,因此,队列中的任务的开始的顺序和添加到队列中的顺序相同.GCD自动的为我们提供了一些调度队列,我们也可以创建新的用于具体的目的. 下面列出几种可用的调度队列类型以及如何使用. (1)serial queues(串行

dispatch queues GCD

我们几乎可以调度队列去完成所有用线程来完成的任务.调度队列相对于线程代码更简单,易于使用,更高效. 下面讲主要简述调度队列,在应用中如何使用调度队列去执行任务. 1.关于调度队列 所有的调度队列都是先进先出队列,因此,队列中的任务的开始的顺序和添加到队列中的顺序相同.GCD自动的为我们提供了一些调度队列,我们也可以创建新的用于具体的目的. 下面列出几种可用的调度队列类型以及如何使用. (1)serial queues(串行队列)又称私有调度队列(private),一般用在对特定资源的同步访问上.

转----详解IOS开发应用之并发Dispatch Queues

详解IOS开发应用之并发Dispatch Queues是本文要介绍的内容,我们几乎可以调度队列去完成所有用线程来完成的任务.调度队列相对于线程代码更简单,易于使用,更高效.下面讲主要简述调度队列,在应用中如何使用调度队列去执行任务. 1.关于调度队列 所有的调度队列都是先进先出队列,因此,队列中的任务的开始的顺序和添加到队列中的顺序相同.GCD自动的为我们提供了一些调度队列,我们也可以创建新的用于具体的目的. 下面列出几种可用的调度队列类型以及如何使用. (1)serial queues(串行队

iOS多线程系列(三)----Dispatch Queues

转载自:http://blog.sina.com.cn/s/blog_6dce99b10101atsu.html,尊重原创! 详解IOS开发应用之并发Dispatch Queues是本文要介绍的内容,我们几乎可以调度队列去完成所有用线程来完成的任务.调度队列相对于线程代码更简单,易于使用,更高效.下面讲主要简述调度队列,在应用中如何使用调度队列去执行任务. 1.关于调度队列 所有的调度队列都是先进先出队列,因此,队列中的任务的开始的顺序和添加到队列中的顺序相同.GCD自动的为我们提供了一些调度队

iOS多线程----Dispatch Queues

转载自:http://blog.sina.com.cn/s/blog_6dce99b10101atsu.html,尊重原创! 详解IOS开发应用之并发Dispatch Queues是本文要介绍的内容,我们几乎可以调度队列去完成所有用线程来完成的任务.调度队列相对于线程代码更简单,易于使用,更高效.下面讲主要简述调度队列,在应用中如何使用调度队列去执行任务. 1.关于调度队列 所有的调度队列都是先进先出队列,因此,队列中的任务的开始的顺序和添加到队列中的顺序相同.GCD自动的为我们提供了一些调度队

iOS 并行编程:GCD Dispatch Queues

1 简介 1.1 功能       Grand Central Dispatch(GCD)技术让任务并行排队执行,根据可用的处理资源,安排他们在任何可用的处理器核心上执行任务.任务可以是一个函数(function)或者是一个block. GCD的底层依然是用线程实现,不过这样可以让程序员不用关注实现的细节. GCD中的队列称为dispatch queue,它可以保证先进来的任务先得到执行通过它能够大大简化多线程编程.工程师只要将要执行的任务(执行代码块)放入队列中,GCD将会为需要执行的任务创建

Dispatch Queues

GCD(Grand Central Dispatch)的 dispatch queues 是一个实现多任务的很好的工具.Dispatch queues 让你能够方便的使用blocks,不管你想要去调用同步或异步.你可以实现几乎所有的以前你通过separate 的threads完成的任务.相对thread的code 而言,dispatch queues的优点是更简单和更有效率. 这章节提供了dispatch queues的详细介绍,所有关于怎么样使用他们去执行普通任务的信息. 一.关于Dispat

GCD介绍(一):基本概念和Dispatch Queues

什么是GCD? Grand Central Dispatch或者GCD,是一套低层API,提供了一种新的方法来进行并发程序编写.从基本功能上讲,GCD有点像NSOperationQueue,他们都允许程序将任务切分为多个单一任务然后提交至工作队列来并发地或者串行地执行.GCD比之NSOpertionQueue更底层更高效,并且它不是Cocoa框架的一部分. 除了代码的平行执行能力,GCD还提供高度集成的事件控制系统.可以设置句柄来响应文件描述符.mach ports(Mach port 用于 O

Dispatch Queues 线程池

Dispatch Queues Dispatch queues are a C-based mechanism for executing custom tasks. A dispatch queue executes tasks either serially or concurrently but always in a first-in, first-out order. (In other words, a dispatch queue always dequeues and start