Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Close stale issues and PRs / stale (push) Successful in 13s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
236 lines
7.3 KiB
Groff
236 lines
7.3 KiB
Groff
.\" Copyright (c) 2008-2012 Apple Inc. All rights reserved.
|
|
.Dd May 1, 2009
|
|
.Dt dispatch_async 3
|
|
.Os Darwin
|
|
.Sh NAME
|
|
.Nm dispatch_async ,
|
|
.Nm dispatch_sync
|
|
.Nd schedule blocks for execution
|
|
.Sh SYNOPSIS
|
|
.Fd #include <dispatch/dispatch.h>
|
|
.Ft void
|
|
.Fo dispatch_async
|
|
.Fa "dispatch_queue_t queue" "void (^block)(void)"
|
|
.Fc
|
|
.Ft void
|
|
.Fo dispatch_sync
|
|
.Fa "dispatch_queue_t queue" "void (^block)(void)"
|
|
.Fc
|
|
.Ft void
|
|
.Fo dispatch_async_f
|
|
.Fa "dispatch_queue_t queue" "void *context" "void (*function)(void *)"
|
|
.Fc
|
|
.Ft void
|
|
.Fo dispatch_sync_f
|
|
.Fa "dispatch_queue_t queue" "void *context" "void (*function)(void *)"
|
|
.Fc
|
|
.Sh DESCRIPTION
|
|
The
|
|
.Fn dispatch_async
|
|
and
|
|
.Fn dispatch_sync
|
|
functions schedule blocks for concurrent execution within the
|
|
.Xr dispatch 3
|
|
framework. Blocks are submitted to a queue which dictates the policy for their
|
|
execution. See
|
|
.Xr dispatch_queue_create 3
|
|
for more information about creating dispatch queues.
|
|
.Pp
|
|
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).
|
|
.Sh TEMPORAL SYNCHRONIZATION
|
|
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
|
|
.Xr pthread 3
|
|
family of procedures, temporal synchronization is accomplished like so:
|
|
.Bd -literal -offset indent
|
|
int r = pthread_mutex_lock(&my_lock);
|
|
assert(r == 0);
|
|
|
|
// critical section
|
|
|
|
r = pthread_mutex_unlock(&my_lock);
|
|
assert(r == 0);
|
|
.Ed
|
|
.Pp
|
|
The
|
|
.Fn dispatch_sync
|
|
function may be used with a serial queue to accomplish the same style of
|
|
synchronization. For example:
|
|
.Bd -literal -offset indent
|
|
dispatch_sync(my_queue, ^{
|
|
// critical section
|
|
});
|
|
.Ed
|
|
.Pp
|
|
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.
|
|
.Pp
|
|
The
|
|
.Fn 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:
|
|
.Bd -literal
|
|
dispatch_async(my_queue, ^{
|
|
// critical section
|
|
});
|
|
.Ed
|
|
.Sh BACKGROUND CONCURRENCY
|
|
.The
|
|
.Fn dispatch_async
|
|
function may be used to execute trivial background tasks on a global concurrent
|
|
queue. For example:
|
|
.Bd -literal
|
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
|
|
// background operation
|
|
});
|
|
.Ed
|
|
.Pp
|
|
This approach is an efficient replacement for
|
|
.Xr pthread_create 3 .
|
|
.Sh COMPLETION CALLBACKS
|
|
Completion callbacks can be accomplished via nested calls to the
|
|
.Fn dispatch_async
|
|
function. It is important to remember to retain the destination queue before the
|
|
first call to
|
|
.Fn 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:
|
|
.Bd -literal
|
|
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);
|
|
});
|
|
}
|
|
.Ed
|
|
.Sh RECURSIVE LOCKS
|
|
While
|
|
.Fn 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
|
|
.Fn 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.
|
|
.Pp
|
|
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).
|
|
.Pp
|
|
Nevertheless, if it is absolutely necessary, what follows is an imperfect way of
|
|
implementing recursive locks using the dispatch framework:
|
|
.Bd -literal
|
|
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;
|
|
});
|
|
}
|
|
.Ed
|
|
.Pp
|
|
The above example does not solve the case where queue A runs on thread X which
|
|
calls
|
|
.Fn dispatch_sync
|
|
against queue B which runs on thread Y which recursively calls
|
|
.Fn 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).
|
|
.Sh IMPLIED REFERENCES
|
|
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:
|
|
.Bd -literal
|
|
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()
|
|
.Ed
|
|
.Pp
|
|
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).
|
|
.Sh FUNDAMENTALS
|
|
Conceptually,
|
|
.Fn dispatch_sync
|
|
is a convenient wrapper around
|
|
.Fn 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
|
|
.Xr dispatch_semaphore_create 3
|
|
for more information about dispatch semaphores. The actual implementation of the
|
|
.Fn dispatch_sync
|
|
function may be optimized and differ from the above description.
|
|
.Pp
|
|
The
|
|
.Fn dispatch_async
|
|
function is a wrapper around
|
|
.Fn dispatch_async_f .
|
|
The application-defined
|
|
.Fa context
|
|
parameter is passed to the
|
|
.Fa function
|
|
when it is invoked on the target
|
|
.Fa queue .
|
|
.Pp
|
|
The
|
|
.Fn dispatch_sync
|
|
function is a wrapper around
|
|
.Fn dispatch_sync_f .
|
|
The application-defined
|
|
.Fa context
|
|
parameter is passed to the
|
|
.Fa function
|
|
when it is invoked on the target
|
|
.Fa queue .
|
|
.Pp
|
|
.Sh SEE ALSO
|
|
.Xr dispatch 3 ,
|
|
.Xr dispatch_apply 3 ,
|
|
.Xr dispatch_once 3 ,
|
|
.Xr dispatch_queue_create 3 ,
|
|
.Xr dispatch_semaphore_create 3
|