dispatch_async(3)

πŸ“¦ version 0~svn197, in ubuntu jammy

πŸ’– dispatch_async(3) πŸ’–

okay bestie, welcome to the Library Functions Manual page for dispatch_async β€” literally the moment your code learns to multitask πŸ’…


✨ 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 *));

✨ DESCRIPTION ✨

so the way that dispatch_async() and dispatch_sync() work is they schedule blocks for concurrent execution inside the dispatch(3) framework. You submit your blocks to a queue, and that queue is the one calling the shots about the policy for their execution. Wanna know how to actually make a queue? go read dispatch_queue_create(3), she explains everything πŸ¦‹

These functions are giving efficient temporal synchronization, background concurrency and data-level concurrency. and the same functions can ALSO be used for efficient notification of the completion of asynchronous blocks (a.k.a. callbacks). like, they do it all. main character energy, no cap ✨


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

okay and here's the slay part: in addition to being a more concise expression of synchronization, this approach is less error prone, because the critical section cannot be accidentally left without restoring the queue to a reentrant state. no more forgetting to unlock your lock, bestie, we simply do not do that here 🫢

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

✨ BACKGROUND CONCURRENCY ✨

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). iconic honestly 🌈


✨ COMPLETION CALLBACKS ✨

Completion callbacks can be accomplished via nested calls to the dispatch_async() function. Now listen up, this part matters: 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);
        });
}

✨ RECURSIVE LOCKS ✨

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. 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. everybody's waiting on everybody. tragic πŸ’€

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). yeah. we said what we said 😭


✨ 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, 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()
bestie. do NOT release the queue inside the block. the comment says NOT SAFE in all caps for a reason. release it outside. i'm begging πŸ™ˆ

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).


✨ FUNDAMENTALS ✨

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.


✨ SEE ALSO ✨


and that's the whole page, bestie!! this one came from Darwin, dated May 1, 2009, and it lives in the <dispatch_async(3)> section of your friendly neighbourhood manual. go schedule something. slay πŸ’–βœ¨

πŸ“¦ where this page comes from