dispatch_queue_create(3)

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

✨ 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 β€” bestie, this is literally where blocks get scheduled for execution πŸ’–

(also hi, this is the dispatch_queue_create(3) page from the Library Functions Manual, so you're in the right place 🫢)


🌸 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_current_queue(void);

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 πŸ¦‹

okay so. Queues are THE fundamental mechanism for scheduling blocks for execution inside the dispatch(3) framework. That's the whole vibe. Everything else is built on this.

Every block you submit to a dispatch queue gets dequeued in FIFO order. And by default, queues made with dispatch_queue_create() will wait for the previously dequeued block to finish before they dequeue the next one. That FIFO-completion behavior is what people casually call a "serial queue." Queues are not tied to any specific thread of execution, and blocks submitted to independent queues may totally run concurrently. Also β€” like all dispatch objects β€” queues are reference counted, and a freshly created queue has a reference count of one. one. iconic.

The optional label argument describes what the queue is for, and it's genuinely useful during debugging and performance analysis. By convention you should pass a reverse DNS style label. If you do provide a label, it is copied. If you don't provide one, then dispatch_queue_get_label() hands you back an empty C string. like, a little empty, but valid. Example time:

my_queue = dispatch_queue_create("com.example.subsystem.taskXYZ", NULL);

The attr argument is reserved for future use and must be NULL. no notes, just NULL.

Queues can be temporarily suspended and resumed with dispatch_suspend() and dispatch_resume() respectively. Suspension is checked prior to block execution and is not preemptive. so it's not gonna yank a block mid-run, it just waits its turn. πŸ’…


πŸ‘‘ MAIN QUEUE πŸ‘‘

The dispatch framework gives you a default serial queue to use. You reach it with dispatch_get_main_queue(). Programs must call dispatch_main() at the end of main() in order to actually process blocks submitted to the main queue. (There are exceptions β€” see the compatibility section, bestie, I got you. πŸ‘€)


🌈 GLOBAL CONCURRENT QUEUES 🌈

Unlike the main queue, or queues you allocated yourself with dispatch_queue_create(), the global concurrent queues schedule blocks as soon as threads become available β€” that's non-FIFO completion order, so the order you get is not the order you gave. slay but chaotic.

The global concurrent queues represent three priority bands:

  • DISPATCH_QUEUE_PRIORITY_HIGH
  • DISPATCH_QUEUE_PRIORITY_DEFAULT
  • DISPATCH_QUEUE_PRIORITY_LOW

Blocks submitted to the high priority global queue will be invoked before those submitted to the default or low priority global queues. And blocks submitted to the low priority global queue only get invoked if no blocks are pending on the default or high priority queues. low priority really said "I'll wait, it's fine." 😭


πŸŽ€ RETURN VALUES πŸŽ€

  • dispatch_queue_create() returns NULL on failure. πŸ’€
  • dispatch_queue_get_label() always returns a valid C string. An empty C string comes back if the label was NULL at creation time.
  • dispatch_get_main_queue() returns the default main queue. 🫢
  • dispatch_get_current_queue() always returns a valid queue. Called from inside a block submitted to a dispatch queue, that queue is what you get back. If it's called from the main thread before dispatch_main() is called, then you get the result of dispatch_get_main_queue(). Otherwise, in all other cases, you get the result of dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0).
  • dispatch_main() never returns. it's giving forever. periodt.

πŸ’œ TARGET QUEUE πŸ’œ

dispatch_set_target_queue() updates the target queue of the given dispatch object. The target queue of an object is the thing responsible for processing that object. Currently, only dispatch queues and dispatch sources are supported by this function. Using dispatch_set_target_queue() with any other dispatch object type? The result is undefined. don't.

The new target queue is retained by the given object before the previous target queue is released. The new target queue takes effect between block executions β€” not in the middle of any existing block execution. non-preemptive, as always. πŸ’…

A dispatch queue inherits its priority from its target queue. So if you wanna change the priority of a queue created with dispatch_queue_create(), use dispatch_get_global_queue() to grab a target queue of the priority you want. 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 be cute with it.

The target queue of a dispatch source specifies where its event handler and cancellation handler blocks will be submitted. Peep dispatch_source_create(3) for more about dispatch sources. πŸ¦‹

The result of passing the main queue or a global concurrent queue to 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. no self-targeting, that's not main character energy, that's a problem.

⚠️ CAVEATS ⚠️

Code cannot make any assumptions about the queue returned by dispatch_get_current_queue(). That 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

Therefore, dispatch_get_current_queue() MUST only be used for identity tests or debugging. that's it. that's the whole job. πŸ™ˆ


🧚 COMPATIBILITY 🧚

Cocoa applications need not call dispatch_main(). Blocks submitted to the main queue will be executed as part of the "common modes" of the application's main NSRunLoop or CFRunLoop. However, blocks submitted to the main queue in applications using dispatch_main() are not guaranteed to execute on the main thread. yeah. heads up.

The dispatch framework is a pure C level API. Because of that, it does not catch exceptions generated by higher level languages such as Objective-C or C++. Applications MUST catch all exceptions before returning from a block submitted to a dispatch queue; otherwise the internal data structures of the dispatch framework will be left in an inconsistent state. bestie please catch your exceptions. I'm begging. 😭

The dispatch framework manages the relationship between dispatch queues and threads of execution. As a result, applications MUST NOT delete or mutate objects that they did not create. 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()
  • pthread_chdir()
  • pthread_fchdir()

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. But 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, one cannot make any assumptions about when the destructor passed to pthread_key_create() is called β€” it may be called between the invocation of blocks on the same queue, or during the idle state of a process. it just does its own thing, honestly. πŸ¦‹

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, since 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 that case, thread specific data such as errno may persist from the block back to the caller. Great care should be taken not to accidentally rely on this side-effect. like, it's a coincidence, not a feature. πŸ™ˆ


πŸ’– SEE ALSO πŸ’–

dispatch(3), dispatch_async(3), dispatch_object(3), dispatch_source_create(3)


okay that's the whole page bestie, go forth and schedule responsibly 🫢✨ this one came out of the Darwin crew and it's dated May 1, 2008 β€” yeah it's an oldie but it still eats. πŸ’…πŸŽ€

πŸ“¦ where this page comes from