| dispatch_async(3) | Library Functions Manual | dispatch_async(3) |
dispatch_async,
dispatch_sync — schedule
blocks for execution
#include
<dispatch/dispatch.h>
void
dispatch_async(dispatch_queue_t
queue, void (^block)(void));
void
dispatch_sync(dispatch_queue_t
queue, void (^block)(void));
void
dispatch_async_f(dispatch_queue_t
queue, void *context, void
(*function)(void *));
void
dispatch_sync_f(dispatch_queue_t
queue, void *context, void
(*function)(void *));
The
dispatch_async()
and dispatch_sync() functions schedule blocks for
concurrent execution within the dispatch(3) framework.
Blocks are submitted to a queue which dictates the policy for their
execution. See dispatch_queue_create(3) for more
information about creating dispatch queues.
These functions support efficient temporal synchronization, background concurrency and data-level concurrency. These same functions can also be used for efficient notification of the completion of asynchronous blocks (a.k.a. callbacks).
Synchronization is often required when multiple threads of execution access shared data concurrently. The simplest form of synchronization is mutual-exclusion (a lock), whereby different subsystems execute concurrently until a shared critical section is entered. In the pthread(3) family of procedures, temporal synchronization is accomplished like so:
int r = pthread_mutex_lock(&my_lock); assert(r == 0); // critical section r = pthread_mutex_unlock(&my_lock); assert(r == 0);
The
dispatch_sync()
function may be used with a serial queue to accomplish the same style of
synchronization. For example:
dispatch_sync(my_queue, ^{
// critical section
});
In addition to providing a more concise expression of synchronization, this approach is less error prone as the critical section cannot be accidentally left without restoring the queue to a reentrant state.
The
dispatch_async()
function may be used to implement deferred critical sections when the result
of the block is not needed locally. Deferred critical sections have the same
synchronization properties as the above code, but are non-blocking and
therefore more efficient to perform. For example:
dispatch_async(my_queue, ^{
// critical section
});
The dispatch_async() function may be used
to execute trivial background tasks on a global concurrent queue. For
example:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
// background operation
});
This approach is an efficient replacement for pthread_create(3).
Completion callbacks can be accomplished via nested calls to the
dispatch_async()
function. It is important to remember to retain the destination queue before
the first call to dispatch_async(), and to release
that queue at the end of the completion callback to ensure the destination
queue is not deallocated while the completion callback is pending. For
example:
void
async_read(object_t obj,
void *where, size_t bytes,
dispatch_queue_t destination_queue,
void (^reply_block)(ssize_t r, int err))
{
// There are better ways of doing async I/O.
// This is just an example of nested blocks.
dispatch_retain(destination_queue);
dispatch_async(obj->queue, ^{
ssize_t r = read(obj->fd, where, bytes);
int err = errno;
dispatch_async(destination_queue, ^{
reply_block(r, err);
});
dispatch_release(destination_queue);
});
}
While dispatch_sync() can replace a lock,
it cannot replace a recursive lock. Unlike locks, queues support both
asynchronous and synchronous operations, and those operations are ordered by
definition. A recursive call to dispatch_sync()
causes a simple deadlock as the currently executing block waits for the next
block to complete, but the next block will not start until the currently
running block completes.
As the dispatch framework was designed, we studied recursive locks. We found that the vast majority of recursive locks are deployed retroactively when ill-defined lock hierarchies are discovered. As a consequence, the adoption of recursive locks often mutates obvious bugs into obscure ones. This study also revealed an insight: if reentrancy is unavoidable, then reader/writer locks are preferable to recursive locks. Disciplined use of reader/writer locks enable reentrancy only when reentrancy is safe (the "read" side of the lock).
Nevertheless, if it is absolutely necessary, what follows is an imperfect way of implementing recursive locks using the dispatch framework:
void
sloppy_lock(object_t object, void (^block)(void))
{
if (object->owner == pthread_self()) {
return block();
}
dispatch_sync(object->queue, ^{
object->owner = pthread_self();
block();
object->owner = NULL;
});
}
The above example does not solve the case
where queue A runs on thread X which calls
dispatch_sync()
against queue B which runs on thread Y which recursively calls
dispatch_sync() against queue A, which deadlocks
both examples. This is bug-for-bug compatible with nontrivial pthread usage.
In fact, nontrivial reentrancy is impossible to support in recursive locks
once the ultimate level of reentrancy is deployed (IPC or RPC).
Synchronous functions within the dispatch framework hold an implied reference on the target queue. In other words, the synchronous function borrows the reference of the calling function (this is valid because the calling function is blocked waiting for the result of the synchronous function, and therefore cannot modify the reference count of the target queue until after the synchronous function has returned). For example:
queue = dispatch_queue_create("com.example.queue", NULL);
assert(queue);
dispatch_sync(queue, ^{
do_something();
//dispatch_release(queue); // NOT SAFE -- dispatch_sync() is still using 'queue'
});
dispatch_release(queue); // SAFELY balanced outside of the block provided to dispatch_sync()
This is in contrast to asynchronous functions which must retain both the block and target queue for the duration of the asynchronous operation (as the calling function may immediately release its interest in these objects).
Conceptually,
dispatch_sync()
is a convenient wrapper around dispatch_async() with
the addition of a semaphore to wait for completion of the block, and a
wrapper around the block to signal its completion. See
dispatch_semaphore_create(3) for more information about
dispatch semaphores. The actual implementation of the
dispatch_sync() function may be optimized and differ
from the above description.
The
dispatch_async()
function is a wrapper around
dispatch_async_f().
The application-defined context parameter is passed to
the function when it is invoked on the target
queue.
The
dispatch_sync()
function is a wrapper around
dispatch_sync_f().
The application-defined context parameter is passed to
the function when it is invoked on the target
queue.
A block submitted with dispatch_async()
runs at a QoS class determined by the block's assigned QoS class and the
target queue's configuration.
By default, a block submitted via
dispatch_async()
is assigned the QoS class current on the calling thread (the value returned
by
qos_class_self()
at the moment of submission). To assign a specific QoS class instead, use
dispatch_block_create_with_qos_class(). To submit a
block with no assigned QoS class, create it with
DISPATCH_BLOCK_NO_QOS_CLASS or
DISPATCH_BLOCK_DETACHED.
A queue created with
dispatch_queue_attr_make_with_qos_class()
has a specified QoS class. Blocks submitted to such a queue with
dispatch_async() run at the queue's specified QoS
class, regardless of their assigned QoS class. A block created with
DISPATCH_BLOCK_ENFORCE_QOS_CLASS preserves its
assigned QoS class instead, as long as that does not result in a lower QoS
class than the queue's specified one.
A queue with a floor configured
via
dispatch_set_qos_class_floor()
runs blocks at the higher of the floor and the block's assigned QoS class.
The floor raises QoS but never lowers it, regardless of any block flags. A
specified or floor QoS class configured on any queue in this queue's target
chain acts as a floor on this queue.
QOS_CLASS_USER_INTERACTIVE
is capped to QOS_CLASS_USER_INITIATED when it is
acquired by propagation (bare blocks, blocks created via
dispatch_block_create()
without an explicit QoS class, and the
DISPATCH_BLOCK_ASSIGN_CURRENT flag). A QoS class set
directly via
dispatch_block_create_with_qos_class()
bypasses the cap, as does a queue's specified QoS class.
See <dispatch/block.h> for the complete resolution rules and the list of block flags.
dispatch(3), dispatch_apply(3), dispatch_once(3), dispatch_queue_create(3), dispatch_semaphore_create(3)
| May 1, 2009 | Darwin |