dispatch_queue_create(3)
okay bestie, sit down, hydrate, because this one is a whole thing. This is the manual page for dispatch_queue_create(3) from the Library Functions Manual β aka the page that tells you where your blocks actually go when you yeet them into the void. Part 1, so buckle up, there's more coming. π
πΈ NAME πΈ
dispatch_queue_create, dispatch_queue_get_label, dispatch_get_current_queue, dispatch_get_global_queue, dispatch_get_main_queue, dispatch_main, dispatch_set_target_queue β where blocks are scheduled for execution π¦
β¨ SYNOPSIS β¨
#include <dispatch/dispatch.h>
dispatch_queue_t
dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
const char *
dispatch_queue_get_label(dispatch_queue_t queue);
dispatch_queue_t
dispatch_get_global_queue(long priority, unsigned long flags);
dispatch_queue_t
dispatch_get_main_queue(void);
void
dispatch_main(void);
void
dispatch_set_target_queue(dispatch_object_t object,
dispatch_queue_t target);
π DESCRIPTION π
Queues are the fundamental mechanism for scheduling blocks for execution inside the dispatch(3) framework. They're the main characters, periodt. π
So here's the tea: every single block you submit to a dispatch queue gets dequeued in FIFO order (first in, first out, no cutting the line bestie π).
Queues you make with the DISPATCH_QUEUE_SERIAL attribute will wait for the previously dequeued block to finish before they dequeue the next one. This FIFO completion behavior is usually just called a "serial queue." And ngl the memory guarantees are iconic: every memory write performed by a block dispatched to a serial queue is guaranteed to be visible to subsequent blocks dispatched to that same queue. Slay.
But here's the part people always get confused about β queues are NOT bound to any specific thread of execution, and blocks submitted to independent queues may run concurrently. It's giving "no loyalty, no strings." π
Queues created with the DISPATCH_QUEUE_CONCURRENT attribute may execute dequeued blocks concurrently, and they support barrier blocks submitted with the dispatch barrier API. π₯
π CREATION π
Queues get created with the dispatch_queue_create() function. And like all dispatch objects, queues are reference counted, and a freshly created queue starts at a reference count of one.
The optional label argument describes the purpose of the queue and is lowkey a lifesaver during debugging and performance analysis. If you provide a label, it gets copied. By convention, clients should pass a reverse DNS style label. For example:
my_queue = dispatch_queue_create("com.example.subsystem.taskXYZ",
DISPATCH_QUEUE_SERIAL);
The attr argument specifies what type of queue to create, and it must be either DISPATCH_QUEUE_SERIAL or DISPATCH_QUEUE_CONCURRENT. No vibes-based guessing allowed. π
The dispatch_queue_get_label() function returns the label that was provided when the given queue was created β or an empty C string if no label was given at creation. And here's a cute trick: passing the constant DISPATCH_CURRENT_QUEUE_LABEL to dispatch_queue_get_label() gives you the label of the current queue. Obsessed. β¨
π΄ SUSPENSION π΄
Queues can be temporarily suspended and resumed with the functions dispatch_suspend() and dispatch_resume() respectively. Important detail though: suspension is checked prior to block execution and is NOT preemptive. So it's not gonna yank a block mid-run, it just says "not yet bestie" at the door. πͺ
π MAIN QUEUE π
The dispatch framework hands your application a default serial queue to use, and you reach it with the dispatch_get_main_queue() function. It's the VIP queue. ποΈ
Programs must call dispatch_main() at the end of main() in order to process blocks submitted to the main queue. (Peep the βCOMPATIBILITYβ section for exceptions, iykyk.) And the dispatch_main() function never returns. Like, never ever. It's not coming back. π
π GLOBAL CONCURRENT QUEUES π
Unlike the main queue or queues you allocate with dispatch_queue_create(), the global concurrent queues schedule blocks as soon as threads become available β that's non-FIFO completion order, so no line, no order, just vibes and availability.
Four global concurrent queues are provided, representing these priority bands:
DISPATCH_QUEUE_PRIORITY_HIGHDISPATCH_QUEUE_PRIORITY_DEFAULTDISPATCH_QUEUE_PRIORITY_LOWDISPATCH_QUEUE_PRIORITY_BACKGROUND
The priority of a global concurrent queue controls the scheduling priority of the threads the system creates to invoke the blocks submitted to that queue. Global queues with lower priority get scheduled for execution after all global queues with higher priority have been scheduled. And on top of that, items on the background priority global queue execute on threads with background state as described in setpriority(2) β meaning disk I/O is throttled and the thread's scheduling priority is set to the lowest value. She's on energy saver mode fr fr. π
Use the dispatch_get_global_queue() function to grab the global queue of a given priority. The flags argument is reserved for future use and must be zero. Passing anything other than zero may result in a NULL return value. So likeβ¦ don't. π«Ά
π― TARGET QUEUE π―
The dispatch_set_target_queue() function updates the target queue of the given dispatch object. The target queue of an object is the one responsible for processing that object.
Some rules, bestie, and they matter:
- The new target queue is retained by the given object before the previous target queue is released.
- The new target queue setting takes effect between block executions on the object β but not in the middle of any existing block executions (non-preemptive).
- The default target queue of all dispatch objects created by the application is the default priority global concurrent queue.
- To reset an object's target queue back to the default, pass the
DISPATCH_TARGET_QUEUE_DEFAULTconstant todispatch_set_target_queue().
The priority of a dispatch queue is inherited from its target queue. So if you want to change the priority of a queue you made with dispatch_queue_create(), use the dispatch_get_global_queue() function to obtain a target queue of the priority you want. That's the move. β¨
Also: blocks submitted to a serial queue whose target queue is another serial queue will not be invoked concurrently with blocks submitted to the target queue β or to any other queue with that same target queue. No sneaky parallelism. π«
Different object types use the target queue for different things:
- The target queue of a dispatch source specifies where its event handler and cancellation handler blocks will be submitted. See
dispatch_source_create(3)for more info about dispatch sources. π¦ - The target queue of a dispatch I/O channel specifies the priority of the global queue where its I/O operations are executed. See
dispatch_io_create(3)for more info about dispatch I/O channels. π - For all other dispatch object types, the only function of the target queue is to determine where an object's finalizer function is invoked.
β οΈ Now the warnings, and I need you to actually read these: - The result of passing the main queue or a global concurrent queue as the first argument of dispatch_set_target_queue() is undefined. - Directly or indirectly setting the target queue of a dispatch queue to itself is undefined. Do not do the ouroboros thing. π
π DEPRECATED FUNCTIONS π
These functions are deprecated and will be removed in a future release:
dispatch_queue_t dispatch_get_current_queue(void);
dispatch_get_current_queue() always returns a valid queue. Here's the breakdown of what you get:
- When called from within a block submitted to a dispatch queue β that queue is returned.
- If called from the main thread before
dispatch_main()is called β the result ofdispatch_get_main_queue()is returned. - In all other cases β the default target queue is returned.
The use of dispatch_get_current_queue() is strongly discouraged, except for debugging and logging purposes. Your code must not make any assumptions about the queue that comes back, unless it's one of the global queues or a queue the code itself created. The returned queue may have arbitrary policies that will absolutely surprise code trying to schedule work with it. The list of policies includes, but is not limited to:
- queue width (i.e. serial vs. concurrent)
- scheduling priority
- security credential
- filesystem configuration
This function is deprecated and will be removed in a future release. π
It is equally unsafe for code to assume that synchronous execution onto a queue is safe from deadlock if that queue is not the one returned by dispatch_get_current_queue().
Also β the result of dispatch_get_main_queue() may or may not equal the result of dispatch_get_current_queue() when called on the main thread. Comparing the two is NOT a valid way to test whether code is executing on the main thread. Instead:
- Foundation/AppKit programs should use
[NSThread isMainThread]. - POSIX programs may use
pthread_main_np(3).
One more cursed detail: dispatch_get_current_queue() may return a queue owned by a different subsystem which has already had all external references to it released. While such a queue will continue to exist until all blocks submitted to it have completed, attempting to retain it is forbidden and will trigger an assertion. And if Objective-C Automatic Reference Counting is enabled, any use of the object returned by dispatch_get_current_queue() will cause retain calls to be automatically generated β so using dispatch_get_current_queue() for any reason in code built with ARC is particularly strongly discouraged. Like, extra extra. π
π COMPATIBILITY π
Okay bestie, this section is the tea β on how dispatch plays with other stuff.
Cocoa apps? They do not need to call dispatch_main(). Blocks you submit to the main queue get executed as part of the "common modes" of the application's main NSRunLoop or CFRunLoop. But β and this is important β blocks submitted to the main queue in applications that do use dispatch_main() are not guaranteed to execute on the main thread. π
The dispatch framework is a pure C level API. That means it does not catch exceptions generated by higher level languages like Objective-C or C++. Applications MUST catch all exceptions before returning from a block submitted to a dispatch queue; otherwise the process will be terminated with an uncaught exception. No cap. π«
The dispatch framework manages the relationship between dispatch queues and threads of execution. Because of that, applications MUST NOT delete or mutate objects that they did not create. And the following interfaces MUST NOT be called by blocks submitted to a dispatch queue:
pthread_cancel()pthread_detach()pthread_join()pthread_kill()pthread_exit()
Applications MAY call the following interfaces from a block submitted to a dispatch queue if and only if they restore the thread to its original state before returning:
pthread_setcancelstate()pthread_setcanceltype()pthread_setschedparam()pthread_sigmask()pthread_setugid_np()
Applications MUST NOT rely on the following interfaces returning predictable results between invocations of blocks submitted to a dispatch queue:
pthread_self()pthread_getschedparam()pthread_get_stacksize_np()pthread_get_stackaddr_np()pthread_mach_thread_np()pthread_from_mach_thread_np()
While the result of pthread_self() may change between invocations of blocks, the value will not change during the execution of any single block. Because the underlying thread may change between block invocations on a single queue, using per-thread data as an out-of-band return value is error prone. In other words, the result of calling pthread_setspecific() and pthread_getspecific() is well defined within a single block, but not across multiple blocks. Also, you cannot make any assumptions about when the destructor passed to pthread_key_create() is called. The destructor may be called between the invocation of blocks on the same queue, or during the idle state of a process. π΅βπ«
The following example code correctly handles per-thread return values:
__block int r;
__block int e;
dispatch_sync(queue, ^{
r = kill(1, 0);
// Copy the per-thread return value to the callee thread
e = errno;
});
printf("kill(1,0) returned %d and errno %d0, r, e);
Note that in the above example errno is a per-thread variable and must be copied out explicitly as the block may be invoked on a different thread of execution than the caller. Another example of per-thread data that would need to be copied is the use of getpwnam() instead of getpwnam_r(). π§
As an optimization, dispatch_sync() invokes the block on the current thread when possible. In this case, the thread specific data such as errno may persist from the block until back to the caller. Great care should be taken not to accidentally rely on this side-effect. π
π SEE ALSO π
That's the whole vibe, bestie. This page comes from Darwin, dated May 1, 2008, and it's the manual for dispatch_queue_create(3). Stay iconic, guard your exceptions, and don't touch threads you didn't create. π
β¨π¦
π¦ 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