dispatch_async(3)
π dispatch_async(3) β schedule blocks for execution π
okay bestie, welcome to the Library Functions Manual entry for dispatch_async(3), and let me tell you, this one is a whole moment. it's giving concurrency, it's giving synchronization, it's giving "your threads are gonna be so organized, periodt". let's get into it π¦
β¨ NAME β¨
dispatch_async, dispatch_sync β schedule blocks for execution
πΈ SYNOPSIS πΈ
#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 *));
like yes, that's the whole vibe up top. four functions, one iconic little family. π
π DESCRIPTION π
so dispatch_async() and dispatch_sync() are the besties that schedule blocks for concurrent execution inside the dispatch(3) framework. the way it works is: you submit blocks to a queue, and that queue is the one calling the shots on the policy for how they get executed. wanna know how to make your own queues? go read dispatch_queue_create(3), it's right there.
these functions are literally built for efficient temporal synchronization, background concurrency and data-level concurrency. and plot twist β the same functions can also do efficient notification of the completion of asynchronous blocks (a.k.a. callbacks). multitasking queen behavior, no cap. π
β³ TEMPORAL SYNCHRONIZATION β³
so when multiple threads of execution are touching shared data at the same time, you need synchronization. the simplest form of that is mutual-exclusion (a lock), where different subsystems run concurrently right up until they enter a shared critical section. in the pthread(3) family of procedures, temporal synchronization goes a lil something like this:
int r = pthread_mutex_lock(&my_lock);
assert(r == 0);
// critical section
r = pthread_mutex_unlock(&my_lock);
assert(r == 0);
now, dispatch_sync() can be used with a serial queue to accomplish the same style of synchronization. for example:
dispatch_sync(my_queue, ^{
// critical section
});
and bestie, not only is this a more concise expression of synchronization, it's also less error prone, because the critical section cannot be accidentally left without restoring the queue to a reentrant state. the vibes are protected. π«Ά
then there's dispatch_async(), which you can use to implement deferred critical sections when you don't need the result of the block locally. deferred critical sections have the same synchronization properties as the code above, but they're non-blocking and therefore more efficient to perform. for example:
dispatch_async(my_queue, ^{
// critical section
});
π BACKGROUND CONCURRENCY π
the dispatch_async() function can 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). she said "new phone, who dis" to thread creation. iconic. π₯
π COMPLETION CALLBACKS π
completion callbacks can be accomplished via nested calls to the dispatch_async() function. now listen, this part is important so i'm saying it in bold energy: you must remember to retain the destination queue before the first call to dispatch_async(), and you must release that queue at the end of the completion callback, so that the destination queue is not deallocated while the completion callback is still 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);
});
}
see how it's nested? blocks in blocks in blocks. it's giving matryoshka doll. π
π RECURSIVE LOCKS π
okay so here's the tea. 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. so a recursive call to dispatch_sync() causes a simple deadlock β the currently executing block waits for the next block to complete, but the next block will not start until the currently running block completes. and they justβ¦ stare at each other forever. π
now, when the dispatch framework was designed, they actually studied recursive locks. and what they found? the vast majority of recursive locks are deployed retroactively when ill-defined lock hierarchies are discovered. as a consequence, adopting recursive locks often mutates obvious bugs into obscure ones. not cute.
the study also revealed an insight: if reentrancy is unavoidable, then reader/writer locks are preferable to recursive locks. disciplined use of reader/writer locks enables reentrancy only when reentrancy is safe (the "read" side of the lock). π§
nevertheless β if it is absolutely necessary β here's 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;
});
}
and yes bestie, 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). so likeβ¦ be aware. π
π 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 β and this is valid because the calling function is blocked waiting for the result of the synchronous function, so it literally 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()
β οΈ asynchronous functions are the opposite, and this is in contrast to them: async functions must retain both the block and the target queue for the duration of the asynchronous operation, because the calling function may immediately release its interest in these objects. so don't get it twisted. β¨
π§ FUNDAMENTALS π§
conceptually, dispatch_sync() is a convenient wrapper around dispatch_async() with the addition of a semaphore to wait for completion of the block, plus a wrapper around the block to signal its completion. (want the semaphore lore? see dispatch_semaphore_create(3).) but heads up β the actual implementation of the dispatch_sync() function may be optimized and differ from the above description. so this is the vibe, not necessarily the literal code. π
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.
π SEE ALSO π
dispatch(3), dispatch_apply(3), dispatch_once(3), dispatch_queue_create(3), dispatch_semaphore_create(3)
and that's the whole page bestie!! π this one came from Darwin, dated May 1, 2009, and it's the dispatch_async(3) manual page itself. go forth and schedule those blocks responsibly. mwah πβ¨
π¦ where this page comes from
- alpine 3.24: libdispatch-doc 6.1.2-r0 Β· Apache-2.0
- arch rolling: libdispatch 6.2.2-1 Β· Apache-2.0
- epel 10: libdispatch 1:6.0.2-1.el10_0 Β· Apache-2.0
- epel 9: libdispatch 1:6.0.2-1.el9 Β· Apache-2.0
- fedora 43: libdispatch 1:6.0.2-3.fc43 Β· Apache-2.0
- fedora 44: libdispatch 1:6.0.2-3.fc43 Β· Apache-2.0
- fedora 44: libdispatch 1:6.0.2-4.fc44 Β· Apache-2.0
- nixos 26.05: swift-corelibs-libdispatch 5.10.1
- opensuse-leap 16.0: libdispatch-devel 5.6.3-bp160.1.1 Β· Apache-2.0
- Copyright (c) 2008-2012 Apple Inc. All rights reserved
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms