Dispatch Queues

Grand Central Dispatch (GCD) dispatch queues是一个执行多任务的强大工具。Dispatch queues让你执行任意blocks的代码,无论它是同步或异步。你可以使用dispatch queues在分开的线程上执行几乎所有的任务。dispatch queues相对于threaded code的优势是他们更加简单和高效。

该章节提供dispatch queues的介绍,并且说明如何在你的应用中使用它们来执行一般的任务。If you want to replace existing threaded code with dispatch queues, you can find some additional tips for how to do that in Migrating Away from Threads.

About Dispatch Queues

Dispatch queues是一种同步或异步执行任务的简单的方式。一个任务是你的应用需要执行的一些简单的工作。例如,你可以定义一个任务来执行一些计算,创建或修改一个数据结构,处理一些从文件读出来的数据,或其他事情。你可以通过在一个函数或block对象中放置对应的代码来定义任务,并将它添加到一个dispatch queue。

一个dispatch queue是一个类似于对象的结构体,它管理着你提交给它的任务。所有dispatch queues都是先进先出数据结构。因此,你添加的任务总是和他们被添加的顺序开始。GCD自动为你提供一些dispatch queues,但你也可以在某些特定的目的下自己创建。

Table 3-1 lists the types of dispatch queues available to your application and how you use them.

  1. 串行队列(also known as private dispatch queues)在一个时间只执行一个任务,以他们被添加到队列的顺序执行。当前正在执行的任务在一个特定的线程执行(可能会随着任务的变化而变化),它由dispatch queue管理。串行队列通常用于同步访问一个特定的资源。
    你可以创建任意多的串行队列,每个队列相对于其他队列是并行执行的。换句话说,如果你创建了4个串行队列,每个队列在一个时间只执行一个任务但是总共会有4个任务在并行的执行,每个队列执行一个。For information on how to create serial queues, see Creating Serial Dispatch Queues.

  2. 并行队列(also known as a type of global dispatch queue)并行地执行一个或多个任务,但任务仍然按照他们被添加到队列的顺序开始。当前正在执行的任务在一个特定的线程执行(可能会随着任务的变化而变化),它由dispatch queue管理。正在执行的任务的确切数目会根据系统条件的变化而变化。
    In iOS 5 and later,你可以通过定义队列类型为DISPATCH_QUEUE_CONCURRENT来创建并行dispatch queues。另外,有4中预定义好的全局并发队列供你使用。For more information on how to get the global concurrent queues, see Getting the Global Concurrent Dispatch Queues.

  3. Main dispatch queue:main dispatch queue是全局都可用的串行队列,它在app的主线程(main thread)上执行任务。该队列的任务在app的run loop上执行,这些任务跟其他绑定在run loop上的事件源的任务交替执行。因为它运行在你的app的main thread上,the main queue常常是一个app同步的关键点。
    虽然你不需要创建main dispatch queue,你应该确保app能适当地消耗它。


当添加并发到一个app的时候,dispatch queues提供许多优于threads的特性。最直接的优点是它有一个简单直接的工作队列编程模型。用threads时,你必须既要写你要执行的代码,又要自己创建和管理threads。Dispatch queues让你把精力集中在你需要执行的代码上,而不必担心线程的创建和管理。取而代之,系统为你处理所有线程的创建和管理。其好处是系统比任何一个简单的app更高效的管理threads。系统能根据可用的资源动态和系统条件来调整threads的数量。另外,相对于你自己创建的线程,系统能更快的开始你的任务。


虽然你可能任务用dispatch queues重写你的代码很困难,但通常使用dispatch queues比用threads代码更容易。编写代码的关键是设计能自控(self-contained)的任务,且能异步运行(着对于threads 和 dispatch queues都是一样的)。然而,dispatch queues在预测性上有一个优势。如果你有两个任务,它们要访问一个共享的resource,但他们运行在不同的线程,不管哪个线程先修改这个resource,你必须使用一个锁来确保这两个任务在同一时间访问它。使用dispatch queues,你可以把这两个任务添加到一个serial dispatch queue来确保在任何时间只有一个任务修改这个resource. 基于队列的同步的类型比锁更加高效,因为无论在竞态或非竞态条件,锁通常都需要深入内核中,这是一个昂贵的开销,然而,一个dispatch queue主要工作在你的app进程空间,他只在必要的时候深入内核。


虽然你会指出两个任务运行在一个串行队列中不是并行的,你必须认识到,两个线程在同一个时间上锁,线程提供的任何并发都会失去的。更重要的是,该线程模式需要创建两个线程,这将增加内核和用户的空间内存。Dispatch queues比threads消耗更少的内存,它内部使用的线程会一直保持忙碌且不会阻塞。


Some other key points to remember about dispatch queues include the following:

  1. Dispatch queues的并发执行是相对于其他Dispatch queues的。任务成串行性限制在一个单一的dispatch queue中。
  2. 由系统决定在某一时间总共会有多少个任务被执行。因此,一个app,它有100个任务在100个不同的队列里,他们可能不是所有的都在并发运行。(除非它有100或更多个可用的核)。
  3. 当选择哪一个新任务开始时,系统会考虑队列的优先级。For information about how to set the priority of a serial queue, see Providing a Clean Up Function For a Queue.
  4. 任务必须在它被添加进队列之前做好了运行的准备。(If you have used Cocoa operation objects before, notice that this behavior differs from the model operations use.)
  5. Private dispatch queues是引用计数的对象。当你在自己的代码中添加了这个queue的引用时,你必须意识到dispatch sources也因被绑定在一个queue上而引用计数也加了1.因此,你必须确保所有dispatch sources将被取消,且通过合适的释放操作来保持引用计数的平衡。For more information about retaining and releasing queues, see Memory Management for Dispatch Queues. For more information about dispatch sources, see About Dispatch Sources.

For more information about interfaces you use to manipulate dispatch queues, see Grand Central Dispatch (GCD) Reference.

In addition to dispatch queues, Grand Central Dispatch provides several technologies that use queues to help manage your code. Table 3-2 lists these technologies and provides links to where you can find out more information about them.


Table 3-2 Technologies that use dispatch queues

  1. Dispatch groups:A dispatch group是监视一系列将要执行的block对象的一种方式。(You can monitor the blocks synchronously or asynchronously depending on your needs.) 。Groups提供一种依赖于其他要执行的任务的有用的同步机制。For more information about using groups, see Waiting on Groups of Queued Tasks.

  2. Dispatch semaphores:A dispatch semaphore is similar to a traditional semaphore but is generally more efficient. Dispatch semaphores call down to the kernel only when the calling thread needs to be blocked because the semaphore is unavailable. If the semaphore is available, no kernel call is made. For an example of how to use dispatch semaphores, see Using Dispatch Semaphores to Regulate the Use of Finite Resources。

  3. Dispatch sources:A dispatch source会响应特定的系统事件而生成notifications。你可以使用dispatch sources来监视如process notifications, signals, and descriptor events among others.当一个事件发生,the dispatch source异步提交你的任务到一个特定的dispatch queue去处理。For more information about creating and using dispatch sources, see Dispatch Sources.

Implementing Tasks Using Blocks

block对象是基于C语言特性的,你可以在你的C,OC和C++代码中使用。blocks能很容易定义一个self-contained的工作单元。虽然他们似乎很像函数指针,一个block在底层用一个数据结构代表,它类似于一个object,它由编译器创建和管理。编译器将你提供的代码打包(也顺带任何相关的数据),将他封装成能在堆中保存的格式,并把它传递给你的app。

blocks其中一个优势是它可以使用在他们语法范围之外的变量。当你在一个函数或方法中定义了一个block,这个block在某种程度上扮演的事传统代码块的角色。例如,一个block可以读取定义在它的parent scope的变量的值。被block访问的变量被拷贝到堆上的block数据结构上,这样block就可以在后面访问他们。当blocks被添加到一个dispatch queue上时,这些变量值必须变为只读格式。然而,被异步执行的blocks可以通过使用__block关键字将变量作为返回值返回给parent’s calling scope。


你在你的代码中声明blocks是通过一个类似于函数指针的语法。一个block和一个function pointer的主要不同点是block name是以(^)为前缀,而不是(*)。就像一个function pointe,你可以向一个block传递参数或接受返回值。Listing 3-1 shows you how to declare and execute blocks synchronously in your code. The variable aBlock is declared to be a block that takes a single integer parameter and returns no value. An actual block matching that prototype is then assigned to aBlock and declared inline. The last line executes the block immediately, printing the specified integers to standard out.


Listing 3-1 A simple block example

int x = 123;
int y = 456;

// Block declaration and assignment
void (^aBlock)(int) = ^(int z) {
    printf("%d %d %d\n", x, y, z);
};

// Execute the block
aBlock(789);   // prints: 123 456 789

The following is a summary of some of the key guidelines you should consider when designing your blocks:

  1. 对于你将要通过一个dispatch queue来异步执行的blocks,从parent function or method中捕获标量变量并在block中使用时安全的。然而,你不应该捕获大的结构体或其他在calling context中申请和释放的指针变量。等到你的block执行的时候,这些指针指向的内存可能已经释放了。Of course, it is safe to allocate memory (or an object) yourself and explicitly hand off ownership of that memory to the block.
  2. Dispatch queues会拷贝添加到它们的blocks,并在完成执行后释放它们。也就是说,你不需要在添加blocks到一个队列时显式拷贝它们。
  3. 虽然在执行小任务时queues比threads更高效,但将blocks放到queue中执行仍然是太过了。如果一个block只是做很少的工作,在内部执行比分配到一个queue中更加便宜。The way to tell if a block is doing too little work is to gather metrics for each path using the performance tools and compare them.
  4. 不要缓存关联在底层线程的数据,并希望在另一个block中访问该数据。如果在同一个queue中的tasks需要共享数据,使用dispatch queue的context pointer来保存数据。For more information on how to access the context data of a dispatch queue, see Storing Custom Context Information with a Queue.
  5. 如果你的block创建多于一个OC对象,you might want to enclose parts of your block’s code in an @autorelease block to handle the memory management for those objects. 虽然GCD dispatch queues有他们自己的autorelease pools,当这些poos耗尽时他们不能保证如此。如果你的app有内存上的限制,创建你自己的autorelease pool来让你以更常规的间隔来释放你的内存。


For more information about blocks, including how to declare and use them, see Blocks Programming Topics. For information about how you add blocks to a dispatch queue, see Adding Tasks to a Queue.

Creating and Managing Dispatch Queues

在你将tasks添加到一个queue之前,你必须选择使用什么类型的queue及如何使用它。Dispatch queues可以串行或并行的执行tasks。另外,如果你心中对queue有一个特定的用法,你可以配置queue的属性。The following sections show you how to create dispatch queues and configure them for use.

Getting the Global Concurrent Dispatch Queues

A concurrent dispatch queue is useful when you have multiple tasks that can run in parallel. A concurrent queue依然是一个先进先出顺序dequeue任务的队列。然而,一个并发队列可能在之前的任务还未执行完之前dequeue新添加的任务。在一个并发队列中执行的任务的确切数目是变化的,他会根据你的app的条件动态变化。许多因素会影响并发队列中运行任务的数目,包括可用的核的数目,进程中正在处理的工作的数目,在其他串行队列上执行的任务的数目和优先级。


系统为每个app提供了4个并发队列。这些队列对app而言是全局的,他们只有在优先级上存在不同。因为他们是全局的,你不需要显式的创建他们。instead,你可以使用dispatch_get_global_queue 函数,as shown in the following example:

dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);


除了得到default concurrent queue,you can also get queues with high- and low-priority levels by passing in the DISPATCH_QUEUE_PRIORITY_HIGH and DISPATCH_QUEUE_PRIORITY_LOW constants to the function instead, or get a background queue by passing the DISPATCH_QUEUE_PRIORITY_BACKGROUND constant. As you might expect, tasks in the high-priority concurrent queue execute before those in the default and low-priority queues. Similarly, tasks in the default queue execute before those in the low-priority queue.


Note: The second argument to the dispatch_get_global_queue function is reserved for future expansion. For now, you should always pass 0 for this argument.


Although dispatch queues are reference-counted objects, you do not need to retain and release the global concurrent queues. Because they are global to your application, retain and release calls for these queues are ignored. Therefore, you do not need to store references to these queues. You can just call the dispatch_get_global_queue function whenever you need a reference to one of them.

Creating Serial Dispatch Queues

当你想让任务以特定顺序执行时,Serial queues是很有用的。A serial queue在一个时间只执行一个任务,通常取队列头部的任务。你可以使用一个serial queue代替锁来保护一个共享资源或可变的数据结构。不同于锁,一个serial queue确保任务以可预知的顺序执行。一旦你异步提交你的任务给一个serial queue,这个queue绝不会死锁。


不同于concurrent queues,你必须显式创建和管理你想使用的serial queues。你可以为你的app创建任意数目的serial queues,但应避免为了同时执行任务而大量创建serial queues。如果你想让大量任务并发执行,你应该将他们提交给一个全局concurrent queues。当创建了serial queues后,争取为每个queue标明一个目的,例如保护一个资环或同步一些关键的行为。


Listing 3-2 shows the steps required to create a custom serial queue. The dispatch_queue_create function takes two parameters: the queue name and a set of queue attributes. The debugger and performance tools display the queue name to help you track how your tasks are being executed. The queue attributes are reserved for future use and should be NULL.


Listing 3-2 Creating a new serial queue

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

In addition to any custom queues you create, the system automatically creates a serial queue and binds it to your application’s main thread. For more information about getting the queue for the main thread, see Getting Common Queues at Runtime.

Getting Common Queues at Runtime

Grand Central Dispatch提供functions来让你访问几个common dispatch queues:

  1. 使用dispatch_get_current_queue函数,用于调试目的或测试当前queue的identity。在一个block对象内部调用该函数,返回的是block提交给的queue(and on which it is now presumably running)。在一个block的外部调用该函数返回你的app的default concurrent queue。
  2. 使用dispatch_get_main_queue函数可以得到与main thread关联的serial dispatch queue。在一个Cocoa应用程序或者在一个调用了dispatch_main function或配置了一个run loop (using either the CFRunLoopRef type or an NSRunLoop object) 时,这个queue会在main thread中自动创建。
  3. Use the dispatch_get_global_queue function to get any of the shared concurrent queues. For more information, see Getting the Global Concurrent Dispatch Queues.

Memory Management for Dispatch Queues

Dispatch queues and other dispatch objects是引用计数的数据类型。当你创建了一个serial dispatch queue,它有一个初始引用计数1.你可以使用dispatch_retain and dispatch_release functions 来增加或减少他们的引用计数。当一个queue的引用计数为0时,系统异步析构这个queue。


保持和释放dispatch objects非常重要,例如,要确保使用queues时他们仍在保持在内存中。作为一个memory-managed Cocoa objects,通用的规则是,如果你计划使用一个queue传递给你的代码,你应该在你使用这个queue前retain它,当你不再使用它时release它。This basic pattern ensures that the queue remains in memory for as long as you are using it.


Note: You do not need to retain or release any of the global dispatch queues, including the concurrent dispatch queues or the main dispatch queue. Any attempts to retain or release the queues are ignored.


即使你使用了垃圾回收的应用程序,你必须仍然retain and release你的dispatch queues and other dispatch objects. Grand Central Dispatch不支持垃圾回收模型。

Storing Custom Context Information with a Queue

…..

Providing a Clean Up Function For a Queue

……

Adding Tasks to a Queue

为了执行一个任务,你必须将它分配给一个合适的dispatch queue。你可以同步或异步地分配tasks,也可以单一的分配或分配到groups中。一旦分配到一个queue,这个queue有责任尽可能快的执行你的任务。This section shows you some of the techniques for dispatching tasks to a queue and describes the advantages of each.

Adding a Single Task to a Queue

有两种方式添加一个任务到一个queue:同步或异步。异步执行使用 dispatch_async and dispatch_async_f functions。当你添加a block object or function to a queue, 没有办法知道代码是否被执行了。因此,异步添加blocks or functions让你安排好了执行代码并继续calling thread的其他工作。尤其重要的是,如果你正在安排你的任务到main thread上时—perhaps in response to some user event.


虽然你应该尽量异步添加你的tasks,仍然有一些时候你需要同步执行一个task来避免竞态条件或其他同步错误。在这种情况下,你可以使用 dispatch_sync and dispatch_sync_f functions to add the task to the queue. 这些函数阻塞当前线程直到特定的任务执行完成。


重要:你决不能在一个task中调用 dispatch_sync or dispatch_sync_f function 并提交到与该task所在的相同的queue中。特别是串行队列,这一定会造成死锁,在并行队列中也应该避免。


The following example shows how to use the block-based variants for dispatching tasks asynchronously and synchronously:

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

Performing a Completion Block When a Task Is Done

从本质上说,分配到一个queue的tasks独立运行于创建他们的代码。然而,当task完成后,你的app可能仍然想被通知到以便合并结果。在传统的异步程序中,你可能会使用回调机制,但在dispatch queues中你可以使用a completion block.

A completion block只是你分配给queue的另一块代码,它在你之前提交的任务的最后面执行。当调用方代码启动任务的时候,它提供一个completion block作为参数。任务代码要做的所有工作只是提交指定的block或函数到特定的queue中。

Listing 3-4 shows an averaging function implemented using blocks. The last two parameters to the averaging function allow the caller to specify a queue and block to use when reporting the results. After the averaging function computes its value, it passes the results to the specified block and dispatches it to the queue. To prevent the queue from being released prematurely, it is critical to retain that queue initially and release it once the completion block has been dispatched.

Listing 3-4 Executing a completion callback after a task

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

Performing Loop Iterations Concurrently

One place where concurrent dispatch queues might improve performance is in places where you have a loop that performs a fixed number of iterations. For example, suppose you have a for loop that does some work through each loop iteration:

for (i = 0; i < count; i++) {
   printf("%u\n",i);
}

If the work performed during each iteration is distinct from the work performed during all other iterations, and the order in which each successive loop finishes is unimportant, you can replace the loop with a call to the dispatch_apply or dispatch_apply_f function. These functions submit the specified block or function to a queue once for each loop iteration. When dispatched to a concurrent queue, it is therefore possible to perform multiple loop iterations at the same time.

You can specify either a serial queue or a concurrent queue when calling dispatch_apply or dispatch_apply_f. Passing in a concurrent queue allows you to perform multiple loop iterations simultaneously and is the most common way to use these functions. Although using a serial queue is permissible and does the right thing for your code, using such a queue has no real performance advantages over leaving the loop in place.

……

Performing Tasks on the Main Thread

Grand Central Dispatch提供一个special dispatch queue,你可以在你的app的main thread上执行任务。This queue is provided automatically for all applications and is drained automatically by any application that sets up a run loop (managed by either a CFRunLoopRef type or NSRunLoop object) on its main thread.如果你没有创建Cocoa的app且不想显式建立一个run loop,你必须调用 dispatch_main function来显式drain the main dispatch queue。你仍可以添加任务到这个queue,但如果你不调用这个函数,这些任务将不被执行。

You can get the dispatch queue for your application’s main thread by calling the dispatch_get_main_queue function. Tasks added to this queue are performed serially on the main thread itself. Therefore, you can use this queue as a synchronization point for work being done in other parts of your application.

Using Objective-C Objects in Your Tasks
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值