GCD 的新特性

Mike Ash Friday Q&A 中文译文:GCD 的新特性

作者 TommyWu
封面圖片: GCD 的新特性

译文 · 原文: Friday Q&A 2011-10-14: What's New in GCD · 作者 Mike Ash

原文:https://www.mikeash.com/pyblog/friday-qa-2011-10-14-whats-new-in-gcd.html 发布:2011-10-14 作者:Mike Ash 译者:MiMo(mimo-v2.5-pro);代码块保留英文原样


祝所有人 iPhone 日快乐!那些还在排队等待或等待快递员的朋友们别担心,因为我来为你们解忧了。今天的话题由 Jon Shier 提议,他让我聊聊 Grand Central Dispatch(大中枢派发)在 Lion 和 iOS 5 中的新特性。

前提阅读
如果你刚接触 GCD,在深入探索 Lion 新特性前需要先熟悉基础内容。网上有很多优秀参考资料,包括我自己的系列文章,从《Grand Central Dispatch 简介,第一部分:基础与调度队列》开始。

概述
GCD 依然是我们熟知并喜爱的那个强大库,但现在增添了更多实用功能。

首先,新增了一个可通过传递 DISPATCH_QUEUE_PRIORITY_BACKGROUND 访问的全局队列。该队列运行优先级极低且磁盘 I / O 会受限制,因此适用于那些需要最小化系统交互影响的持续性任务。

接下来,我们现在具备创建自定义并发队列的能力。之前自定义队列始终是串行的,而 GCD 支持的并发队列仅限于全局队列。自定义并发队列允许轻松暂停并行任务。与此同时,GCD 现在提供了 dispatch barrier(派发屏障),这使得自定义并发队列可以像读写锁一样使用。

最后,我们迎来了期待已久的 GCD I/O 支持。可以为路径或文件描述符创建 Dispatch IO 对象。这不仅比 dispatch source API 提供了更简洁的 I/O 接口,还允许 GCD 更智能地协调 I/O 活动以避免磁盘抖动。为此,还引入了一种新的 dispatch data(派发数据)类型,它能高效地管理非连续数据。

关于新的全局后台队列确实没什么可多说的,所以我们直接进入……

自定义并发队列与屏障

创建自定义并发队列很简单:向 dispatch_queue_create 函数传入 DISPATCH_QUEUE_CONCURRENT。串行队列仍然可以通过传入 NULLDISPATCH_QUEUE_SERIAL 来获取。

一旦创建,一个并发队列(concurrent queue)就会按照预期工作。只要系统负载和能力允许,提交给它的多个块可以并行运行。与全局队列(global queues)不同,您仍然可以挂起 / 恢复自定义并发队列,这使得它对于管理一组并行操作非常有用。

调度屏障(Dispatch barriers)与自定义并发队列配合使用。它们可以与两个函数一起使用:dispach_barrier_asyncdispatch_barrier_sync。这些函数的工作方式与 dispatch_asyncdispatch_sync 类似,不同之处在于,如果在自定义并发队列上使用,通过屏障函数提交的块不会与该队列上的其他工作并发运行。相反,它会等待队列上当前正在执行的任何任务完成,然后在屏障块执行期间阻塞该队列上的其他所有任务。一旦屏障完成,执行将正常恢复。

请注意,这些屏障函数在串行队列上毫无意义,因为串行队列上的每一个工作单元都会阻塞其他工作。当它们用于全局队列时,同样不起作用 —— 此时它们只会执行非屏障的 dispatch_asyncdispatch_sync。这是因为全局队列是共享资源,允许单个组件为所有人阻塞它们没有意义。

自定义并发队列与屏障的配合,可以高效地操作那些能够并发读取但不能并发写入的数据结构。如果你熟悉传统的多线程技术,它们本质上提供了与读写锁相同的能力。

举个例子,假设我们有一个被用作缓存的 NSMutableDictionaryNSMutableDictionary 的读取是线程安全的,但在修改其内容时不允许任何并发访问 —— 即使其他操作是简单的读取也不行。

使用自定义并发队列和屏障可以轻松实现这一点。首先,我们将创建字典和队列:

_cache = [[NSMutableDictionary alloc] init];
_queue = dispatch_queue_create("com.mikeash.cachequeue", DISPATCH_QUEUE_CONCURRENT);

要从缓存中读取,我们可以直接使用 dispatch_sync

- (id)cacheObjectForKey: (id)key
{
__block obj;
dispatch_sync(_queue, ^{
obj = [[_cache objectForKey: key] retain];
});
return [obj autorelease];
}

由于队列是并发的,这允许并发访问缓存,因此在常见情况下不会出现多个线程之间的争用。

写入缓存时,我们需要一个屏障:

- (void)setCacheObject: (id)obj forKey: (id)key
{
dispatch_barrier_async(_queue, ^{
[_cache setObject: obj forKey: key];
});
}

由于这段代码使用了屏障函数(barrier function),它确保了在代码块运行期间对缓存的独占访问。运行时不仅会排除所有其他对缓存的写入操作,还会排除所有其他读取操作,从而使修改操作变得安全。

对于如此简单的字典结构而言,这种提升并不算太突出。但在更复杂的使用场景中 —— 比如读取方需要执行一系列耗时的原子操作(atomic operations)时 —— 它能让编写快速、安全的并发代码变得容易。

Dispatch Data
Dispatch data 对象显然是为促进 dispatch IO 而引入的,但它们本身也具有独立性,可作为通用数据容器使用。这些对象与 NSData 对象非常相似,都是对原始指针和长度的简单对象包装。数据的含义和使用方式完全由你决定。

不过,它与 NSData 有一个主要区别,即 dispatch data(调度数据)对象可以是非连续的。从根本上讲,一个 NSData 是一个单一的缓冲区。而一个 dispatch data 对象则是一组可能包含许多此类缓冲区的集合。这能显著提高性能,因为很多时候根本不需要复制数据。例如,当连接两个 NSData 对象时,至少需要复制其中一个缓冲区,很可能两个都要复制。而连接 dispatch data 对象时,无需复制任何内容。在内部,这是通过创建一个 dispatch data 对象的树状结构来实现的,其中叶子节点包含一个单独的连续缓冲区,而其他节点则指向包含各个缓冲区的对象。

当然,很多代码希望处理连续的数据,但幸运的是,GCD 可以轻松地将一个 dispatch data 对象合并(squash)成一个单一的缓冲区。对于更灵活的代码,则可以轻松地遍历数据对象内包含的各个单独缓冲区。

要创建一个基本的 dispatch data(分发数据)对象,请使用 dispatch_data_create 函数。它接受一个指针、一个长度、一个析构器 block(代码块),以及一个用于运行该析构器的队列。通过请求默认析构器,数据将立即复制到由 GCD 管理的存储区域:

dispatch_data_t data = dispatch_data_create(buffer, length, NULL, DISPATCH_DATA_DESTRUCTOR_DEFAULT);
// buffer can now be freed

当然,使用这套机制的全部意义正是为了避免复制,所以最佳做法是提供一个显式析构函数来释放内存,从而避免被复制。一个常见场景是使用 malloc 分配的内存,而 DISPATCH_DATA_DESTRUCTOR_FREE 这个析构函数会调用 free 函数:

void *buffer = malloc(length);
// fill out buffer
dispatch_data_t data = dispatch_data_create(buffer, length, NULL, DISPATCH_DATA_DESTRUCTOR_FREE);
// buffer will now be freed when data is destroyed

对于其他类型的缓冲区,我们可以提供一个自定义块(block)来执行必要的操作。例如,这里有一个简单的函数,它创建了一个包裹着 NSData 对象的 dispatch data 对象:

dispatch_data_t CreateDispatchDataFromNSData(NSData *nsdata)
{
// copy the data object here, in case it's mutable
// this will just be a retain if it's immutable
nsdata = [nsdata copy];
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
return dispatch_data_create([nsdata bytes], [nsdata length], queue, ^{
// balance the copy at the top
[nsdata release];
});
}

要拼接两个 dispatch data 对象,只需使用 dispatch_data_create_concat。要提取数据对象的一部分,dispatch_data_create_subrange 可以获取精确的子范围,而 dispatch_data_copy_region 则能获取特定位置附近的区域,这赋予了 GCD 更大的灵活性以提高效率。

要访问数据对象的内容,最简单的方式是调用 dispatch_data_create_map,它会将所有数据拼接成一个连续的内存缓冲区:

const void *buffer;
size_t length;
dispatch_data_t tmpData = dispatch_data_create_map(data, &buffer, &length);
// use buffer and length here
dispatch_release(tmpData);

然而,这种方式可能需要拷贝各个数据片段以创建一个连续的缓冲区,而这正是整个系统试图避免的。为了更高效地访问数据内容,可以使用 dispatch_data_apply 函数,它将遍历各个片段,并对每个片段调用一个代码块。

例如,以下是一个使用 dispatch_data_apply 将包含 ASCII 数据的数据对象转换为 NSString 的函数,从而避免不必要的拷贝:

NSString *StringFromDispatchData(dispatch_data_t data)
{
NSMutableString *s = [NSMutableString stringWithCapacity: dispatch_data_get_size(data)];
dispatch_data_apply(data, ^(dispatch_data_t region, size_t offset, const void *buffer, size_t size) {
[s appendFormat: @"%.*s", size, buffer];
return (_Bool)true;
});
return s;
}

注意,applier block(回调块)返回一个布尔值,用于指示是否应继续执行应用操作。由于该回调块会无条件继续执行,因此它仅返回 true,并附加了一些类型转换以消除编译器警告。

Dispatch IO 现在我们来看看 GCD 中真正重要的新特性。最初的 GCD API 通过 dispatch 源(dispatch source)来集成 IO 操作。dispatch 源可用于监视文件描述符(file descriptor),并在可读写数据时运行处理程序。然而,这种方式仍需程序员承担大量工作 —— 必须手动读写相关数据并管理文件描述符的生命周期。通过将更多职责移交给 GCD,系统能更智能地管理多个并发 IO 操作,从而减少内存颠簸(thrash)和资源争用(resource contention)。

Dispatch IO 对象被称为通道(channel)。通道封装了一个文件描述符。要创建通道,需使用dispatch_io_create函数,该函数接受通道类型(流式或随机访问)、文件描述符、与通道关联的队列以及清理处理程序。以下是一个为标准输入创建通道的简单示例:

dispatch_io_t stdinChannel = dispatch_io_create(DISPATCH_IO_STREAM, STDIN_FILENO, dispatch_get_global_queue(0, 0), ^(int error) {
if(error)
fprintf(stderr, "got an error from stdin: %d (%s)\n", error, strerror(error));
});

我们希望能从该通道读取数据,但首先需要对其进行配置。可以通过为通道设置高低水位值来告诉 GCD 我们期望的数据读取频率。低水位值决定了 GCD 在调用读取处理程序前会尝试收集的数据量。标准输入通常是交互式的,这种情况下我们希望 GCD 在接收到任何数据时立即调用读取处理程序 —— 无论数据量多小,因此我们将低水位标记设置为仅一个字节:

dispatch_io_set_low_water(stdinChannel, 1);

没有理由限制最大数据量,因此我们将高水位标记(high water)保持在默认值 SIZE_MAX,这本质上意味着无限制。如果出于某种原因需要限制,只需调用 dispatch_io_set_high_water 即可。

还有一个 dispatch_io_set_interval 函数,它会指示 GCD 周期性地调用读取处理器(read handler),以便代码监控 IO 操作的进度。对于从标准输入进行简单读取,这同样是不必要的。

现在通道已配置完毕,我们将使用 dispatch_io_read 告诉 GCD 从该通道读取数据。此函数接受一个通道、一个偏移量(在像这样的流式通道(stream channels)中会被忽略)、一个长度、一个队列以及一个处理器代码块(handler block):

dispatch_io_read(stdinChannel, 0, SIZE_MAX, dispatch_get_global_queue(0, 0), ^(bool done, dispatch_data_t data, int error) {
if(data)
{
// process data
}
if(error)
{
// handle an error, or just let the channel's handler take care of it
}
if(done)
{
// we've processed all of stdin, so exit
exit(0);
}
});

同样,我们可以使用 dispatch_io_write 写入一个通道(channel)。通过创建另一个通道并在上述处理程序中插入一段代码,程序就变成了一个简单的回声工具:

dispatch_io_t stderrChannel = dispatch_io_create(DISPATCH_IO_STREAM, STDERR_FILENO, dispatch_get_global_queue(0, 0), ^(int error) {
if(error)
fprintf(stderr, "got an error from stdout: %d (%s)\n", error, strerror(error));
});
dispatch_io_read(stdinChannel, 0, SIZE_MAX, dispatch_get_global_queue(0, 0), ^(bool done, dispatch_data_t data, int error) {
if(data)
{
dispatch_io_write(stderrChannel, 0, data, dispatch_get_global_queue(0, 0), ^(bool done, dispatch_data_t data, int error) {});
}
...

除了直接处理原始文件描述符(file descriptors),GCD 还提供了 dispatch_io_create_with_path 这一便捷函数,用于直接获取磁盘文件对应的 IO 通道(IO channel)。这实质上是将 dispatch_io_createopen 函数结合在一起,方便开发者只需在一个地方处理错误。

使用完 IO 通道后,只需调用 dispatch_io_close 来显式关闭通道,同时别忘了调用 dispatch_release 以平衡创建时的引用计数。

对于简单用例,GCD 还提供了 dispatch_readdispatch_write 调用,它们无需设置通道即可基于文件描述符进行简单的 GCD 风格 IO 操作。这使得一次性读取一块数据变得简单,尽管对于更复杂的场景,创建通道会更高效且更易用。

Dispatch IO 通道几乎可以与任何类型的文件描述符通信,因此该 API 不仅适用于操作文件,也同样适用于套接字(sockets)和管道(pipes)。

总结

Mac OS X Lion 和 iOS 5 为 GCD(Grand Central Dispatch)带来了一些令人兴奋且期待已久的新功能。新的全局后台队列适用于长时间运行、影响较低的任务。创建自定义并发队列的能力能更好地管理并行化任务,而屏障(barrier)则能在读取时实现对共享数据的安全并行访问,并在写入时实现独占访问。最后,dispatch IO API 将 GCD 的智能特性和系统级集成能力带到了文件访问和网络通信中。

今天的内容就到这里。你现在可以继续去等待你那闪闪发光的新 iPhone 了。Friday Q & A 栏目由读者的想法驱动,所以如果你在把玩新设备时恰好想到希望这里讨论的话题,请发过来!


#Original (English)

Source: https://www.mikeash.com/pyblog/friday-qa-2011-10-14-whats-new-in-gcd.html

Happy iPhone Day to all! For those of you stuck waiting in line or waiting for the delivery guy, never fear, for I have the solution to your pain. For today’s topic, Jon Shier suggested that I talk about the new features in Grand Central Dispatch in Lion and iOS 5.

Prerequisite ReadingIf you’re new to GCD, you’ll want to become familiar with the basics before diving into the new features in Lion. There are many good references out there, including my own series which starts off with Intro to Grand Central Dispatch, Part I: Basics and Dispatch Queues.

OverviewGCD is still the same great library that we know and love, but now has a bunch more goodies.

First, there’s a new global queue available, which can be accessed by passing DISPATCH_QUEUE_PRIORITY_BACKGROUND. This queue runs at an extremely low priority and disk IO is throttled. This makes it suitable for ongoing tasks which need to have a minimal impact on the system’s interactive use.

Next, we now have the ability to create custom concurrent queues. Previously, custom queues were always serial, and the only concurrent queues supported by GCD were the global queues. Custom concurrent queues allow easy suspension of parallelized tasks. Alongside this, GCD now provides dispatch barriers, which allow a custom concurrent queue to be used much like a reader/writer lock.

Finally, we have the long-awaited GCD for IO. Dispatch IO objects can be created for paths or file descriptors. This not only provides a simpler interface for IO compared to the dispatch source API, but it also allows GCD to more intelligently coordinate IO activities to avoid disk thrashing. To go along with this, there’s a new dispatch data type, which efficietly manages noncontiguous data.

There isn’t really much more to say about the new global background queue, so let’s jump straight to…

Custom Concurrent Queues and BarriersCreating a custom concurrent queue is easy: pass DISPATCH_QUEUE_CONCURRENT to the dispatch_queue_create function. Serial queues can still be obtained by passing NULL or DISPATCH_QUEUE_SERIAL.

Once created, a concurrent queue acts just as you’d expect. Multiple blocks submitted to it can run in parallel if system load and capabilities permit. Unlike the global queues, you can still suspend/resume custom concurrent queues, making it useful for managing a set of parallel operations.

Dispatch barriers go along with custom concurrent queues. They can be used with two functions: dispach_barrier_async and dispatch_barrier_sync. These work just like dispatch_async and dispatch_sync except that, if used on a custom concurrent queue, the block that’s submitted with the barrier function doesn’t run concurrently with other work on that queue. Instead, it waits until anything currently executing on the queue is finished, then blocks everything else on the queue while the barrier block executes. Once the barrier completes, execution resumes normally.

Note that these barrier functions are pointless on a serial queue, since every unit of work blocks other work on such a queue. They are non-functional when used on the global queues, where they simply do a non-barrier dispatch_async or dispatch_sync. This is because the global queues are a shared resource and it doesn’t make sense to allow a single component to block them for everybody.

Custom concurrent queues and barriers allow for the efficient manipulation of data structures which can be read but not written concurrently. If you’re familiar with them, they allow essentially the same capabilities as reader/writer locks from more traditional multithreading techniques.

As an example, let’s imagine that we have a NSMutableDictionary that’s being used as a cache. NSMutableDictionary is thread safe for reading, but doesn’t allow any concurrent access while modifying its contents, not even if the other access is simple reading.

This can easily be done using a custom concurrent queue and barriers. First, we’ll create the dictionary and the queue:

_cache = [[NSMutableDictionary alloc] init];
_queue = dispatch_queue_create("com.mikeash.cachequeue", DISPATCH_QUEUE_CONCURRENT);

To read from the cache, we can just use a dispatch_sync:

- (id)cacheObjectForKey: (id)key
{
__block obj;
dispatch_sync(_queue, ^{
obj = [[_cache objectForKey: key] retain];
});
return [obj autorelease];
}

Because the queue is concurrent, this allows for concurrent access to the cache, and therefore no contention between multiple threads in the common case.

To write to the cache, we need a barrier:

- (void)setCacheObject: (id)obj forKey: (id)key
{
dispatch_barrier_async(_queue, ^{
[_cache setObject: obj forKey: key];
});
}

Because this uses the barrier function, it ensures exclusive access to the cache while the block runs. Not only does it exclude all other writes to the cache while it runs, but it also excludes all other reads, making the modification safe.

The gain for such a simple dictionary isn’t too compelling, but for more complicated use cases, where the readers need to carry out an expensive sequence of atomic operations, it can make it easy to write fast, safe concurrent code.

Dispatch DataDispatch data objects are clearly included to facilitate dispatch IO, but they stand on their own and can be used as general purpose data containers as well. Dispatch data objects are much like NSData objects, in that they’re simple object wrappers around a raw pointer and length. What the data means and how you use it is completely up to you.

There’s a major difference from NSData, though, which is that dispatch data objects can be noncontiguous. Fundamentally, an NSData is a single buffer. A dispatch data object is a collection of potentially many such buffers. This can dramatically increase performance because there’s often no need to copy data around at all. For example, when concatenating two NSData objects together, at least one of the buffers needs to be copied, and likely both. When concatenating dispatch data objects, nothing needs to be copied. Internally, this is implemented by creating a tree of dispatch data objects, with the leaves containing a single contiguous buffer, and others pointing to the objects that contain the individual buffers.

Of course, a lot of code wants to work with contiguous data, but fortunately GCD makes it easy to squash a dispatch data object together so that it becomes a single buffer. For code that’s more flexible, it’s easy to iterate over the individual buffers contained within the data object.

To create a basic dispatch data object, use the dispatch_data_create function. It takes a pointer, a length, a destructor block, and a queue on which to run the destructor. By requesting the default destructor, the data will be immediately copied into storage that’s managed by GCD:

dispatch_data_t data = dispatch_data_create(buffer, length, NULL, DISPATCH_DATA_DESTRUCTOR_DEFAULT);
// buffer can now be freed

Of course, the whole point of this stuff is to avoid copies, so it’s better to provide an explicit destructor that will free the memory so that it can avoid being copied. A common case is memory allocated with malloc, and the DISPATCH_DATA_DESTRUCTOR_FREE destructor will call free:

void *buffer = malloc(length);
// fill out buffer
dispatch_data_t data = dispatch_data_create(buffer, length, NULL, DISPATCH_DATA_DESTRUCTOR_FREE);
// buffer will now be freed when data is destroyed

For other types of buffers, we can provide a custom block to do whatever is necessary. For example, here’s a simple function that creates a dispatch data object wrapping an NSData object:

dispatch_data_t CreateDispatchDataFromNSData(NSData *nsdata)
{
// copy the data object here, in case it's mutable
// this will just be a retain if it's immutable
nsdata = [nsdata copy];
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
return dispatch_data_create([nsdata bytes], [nsdata length], queue, ^{
// balance the copy at the top
[nsdata release];
});
}

To concatenate two dispatch data objects, just use dispatch_data_create_concat. To extract a piece of a data object, dispatch_data_create_subrange will grab an exact subrange, and dispatch_data_copy_region will grab a region around a specific location, which gives GCD more flexibility to be efficient.

To access the contents of a data object, the simplest way is to call dispatch_data_create_map which concatenates all of the data into a single contiguous buffer:

const void *buffer;
size_t length;
dispatch_data_t tmpData = dispatch_data_create_map(data, &buffer, &length);
// use buffer and length here
dispatch_release(tmpData);

However, this may need to copy the individual data pieces in order to create a contiguous buffer, which is just what this whole system is trying to avoid. For more efficient access to the contents, dispatch_data_apply is available and will walk through the individual pieces, calling a block on each one.

For example, here is a function which transforms a data object containing ASCII data into an NSString using dispatch_data_apply to avoid unnecessary copies:

NSString *StringFromDispatchData(dispatch_data_t data)
{
NSMutableString *s = [NSMutableString stringWithCapacity: dispatch_data_get_size(data)];
dispatch_data_apply(data, ^(dispatch_data_t region, size_t offset, const void *buffer, size_t size) {
[s appendFormat: @"%.*s", size, buffer];
return (_Bool)true;
});
return s;
}

Note that the applier block returns a boolean indicating whether the apply operation should continue. Since this block continues unconditionally, it just returns true, with some typecasting to placate the compiler.

Dispatch IONow we reach the really big new feature in GCD. The original GCD API could integrate with IO through dispatch sources. A dispatch source could be used to monitor a file descriptor and run a handler when data could be read or written. However, this approach still left a lot up to the programmer, who had to manually read and write the data in question and manage file descriptor lifetime. By handing more responsibility over to GCD, it’s also able to more intelligently manage multiple concurrent IO operations to reduce thrash and resource contention.

Dispatch IO objects are called channels. A channel wraps a file descriptor. To create one, use the dispatch_io_create function, which takes a channel type (stream or random access), the file descriptor, a queue to associate with the channel, and a cleanup handler. Here’s a quick example of making a channel for standard input:

dispatch_io_t stdinChannel = dispatch_io_create(DISPATCH_IO_STREAM, STDIN_FILENO, dispatch_get_global_queue(0, 0), ^(int error) {
if(error)
fprintf(stderr, "got an error from stdin: %d (%s)\n", error, strerror(error));
});

We’ll want to read from this channel, but first we need to configure it. We can tell GCD how often we want data by setting high and low water values for the channel. The low water value sets how much data GCD will try to gather before invoking a read handler. Standard input is often interactive, in which case we want GCD to invoke the read handler when any data comes in no matter how small, so we’ll set the low water mark to just one byte:

dispatch_io_set_low_water(stdinChannel, 1);

There’s no reason to limit the maximum amount of data, so we’ll leave the high water value at the default of SIZE_MAX, which is essentially infinite. If we had to limit this for some reason, we’d just call dispatch_io_set_high_water.

There’s also a dispatch_io_set_interval function, which tells GCD to call the read handler periodically, allowing the code to monitor the progress of the IO operations. This is also unnecessary for simply reading from standard input.

Now that the channel is configured, we’ll tell GCD to read from it using dispatch_io_read. This function takes a channel, an offset (ignored in the case of stream channels like this one), a length, a queue, and a handler block:

dispatch_io_read(stdinChannel, 0, SIZE_MAX, dispatch_get_global_queue(0, 0), ^(bool done, dispatch_data_t data, int error) {
if(data)
{
// process data
}
if(error)
{
// handle an error, or just let the channel's handler take care of it
}
if(done)
{
// we've processed all of stdin, so exit
exit(0);
}
});

Similarly, we can write to a channel using dispatch_io_write. By creating another channel and inserting a bit of code into the above handler, the program turns into a simple echo tool:

dispatch_io_t stderrChannel = dispatch_io_create(DISPATCH_IO_STREAM, STDERR_FILENO, dispatch_get_global_queue(0, 0), ^(int error) {
if(error)
fprintf(stderr, "got an error from stdout: %d (%s)\n", error, strerror(error));
});
dispatch_io_read(stdinChannel, 0, SIZE_MAX, dispatch_get_global_queue(0, 0), ^(bool done, dispatch_data_t data, int error) {
if(data)
{
dispatch_io_write(stderrChannel, 0, data, dispatch_get_global_queue(0, 0), ^(bool done, dispatch_data_t data, int error) {});
}
...

In addition to simply dealing with raw file descriptors, GCD also provides dispatch_io_create_with_path, a convenience function for directly getting an IO channel to a file on disk. This essentially combines dispatch_io_create and open, with the convenience of only having to handle errors in one spot.

When done with an IO channel, simply call dispatch_io_close to explicitly close the channel, and don’t forget a dispatch_release to balance the create call.

For simple use cases, GCD also provides dispatch_read and dispatch_write calls, which do simple, GCD-based IO from a file descriptor without having to set up channels. This makes it simple to just read a chunk of data all at once, although for more complicated uses, creating a channel is more efficient and easier to work with.

Dispatch IO channels can talk to just about any kind of file descriptor, making this API useful not only for manipulating files, but also sockets and pipes.

ConclusionLion and iOS 5 bring some exciting and long-awaited additions to GCD. The new global background queue is useful for long-running, low impact tasks. The ability to create custom concurrent queues allows much better management of parallelized tasks, and barriers allow for safe parallel access to shared data while reading and exclusive access for writing. Finally, the dispatch IO API brings GCD’s smarts and system-wide integration to file access and networking.

That’s it for today. You may now return to waiting for your shiny new iPhone. Friday Q&A is driven by reader ideas, so if you happen to come up with something you’d like to see covered here while you’re playing with your new toy, send it in!