Published on

GCD Reference

GCD

A key concept in GCD is the queue. The core idea: break a long-running task into multiple units of work and add those units to a dispatch queue. The system manages these queues for us, executing the work units across multiple threads. We don't need to start or manage background threads ourselves.

Tasks

A task is an operation — in GCD, it's a block. Tasks have two execution modes: synchronous and asynchronous. The difference is whether the current thread is blocked.

  • Synchronous execution: Blocks the current thread and waits for the block to finish before the current thread continues. The compiler may optimize this, so sometimes you'll find the block actually executes on the current thread without creating a new one.

  • Asynchronous execution: The current thread continues immediately without blocking.

Queues

A queue holds tasks. There are two types: serial queues and concurrent queues.

  • Serial queue: GCD dequeues tasks one at a time in FIFO order — execute one, then pull the next, and so on.

  • Concurrent queue: GCD dequeues tasks in FIFO order, but immediately dispatches each to a different thread. Because tasks are pulled and dispatched quickly, they appear to run simultaneously. GCD controls the level of concurrency based on system resources; when there are many tasks, not all run at the same time.

GCD provides three types of queues:

  • The main queue: Functions like the main thread. Tasks submitted to the main queue execute on the main thread. Obtain it with dispatch_get_main_queue(). Because it is tied to the main thread, it is a serial queue.

  • Global queues: Concurrent queues shared across the entire process. There are three global queues with high, medium (default), and low priority. Access them with dispatch_get_global_queue, passing the desired priority.

  • User queues: (GCD doesn't have an official name for these, but we call them user queues.) Created with dispatch_queue_create. These queues are serial, which makes them useful for synchronization.

Deadlocks

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"Task executing");
    });
}

The code above will deadlock. Why: dispatch_sync blocks the current thread (the main thread), while at the same time it must execute NSLog(@"Task executing") on the main thread (dispatch_get_main_queue()). Each is waiting on the other, causing a deadlock.

Deadlocks aren't limited to the main thread; they're just most visible there because any user input becomes unresponsive.

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    dispatch_queue_t queue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        NSLog(@"1 in %@", [NSThread currentThread]);
        dispatch_sync(queue, ^{
            NSLog(@"Task executing");
        });
        NSLog(@"2 in %@", [NSThread currentThread]);
    });
}

NSLog(@"Task executing") will never run. Reason: queue is a serial queue (DISPATCH_QUEUE_SERIAL, also written as NULL). dispatch_async(queue, ...) dispatches work to a new thread separate from the main thread. After printing NSLog(@"1 in %@", [NSThread currentThread]), dispatch_sync blocks queue while also trying to execute NSLog(@"Task executing") on queue. Since queue is blocked waiting for itself, it deadlocks.

In short: never synchronously dispatch to a serial queue from within a block already running on that same serial queue. The following two examples are both safe:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"Task executing");
    });
});

dispatch_queue_t queue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
    NSLog(@"1 in %@", [NSThread currentThread]);
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"Task executing");
    });
    NSLog(@"2 in %@", [NSThread currentThread]);
});

Common Usage Patterns

  • dispatch_async with a global concurrent queue:
// Get the global concurrent queue
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// Add a task to the global queue for async execution
dispatch_async(queue, ^{
    NSLog(@"-----task---%@", [NSThread currentThread]);
});
  • dispatch_async with a serial queue:
// 1. Create a serial queue
dispatch_queue_t queue = dispatch_queue_create("cn.ibloodline.queue", NULL);
// 2. Add a task to the serial queue for async execution
dispatch_async(queue, ^{
    NSLog(@"-----task---%@", [NSThread currentThread]);
});
// 3. Non-ARC: release the created queue
// dispatch_release(queue);
  • dispatch_async with the main queue:
// 1. Main queue (tasks added to the main queue automatically run on the main thread)
dispatch_queue_t queue = dispatch_get_main_queue();
// 2. Add a task to the main queue for async execution (still executes serially)
dispatch_async(queue, ^{
    NSLog(@"-----task---%@", [NSThread currentThread]);
});
  • dispatch_sync with the main queue (deadlock):
dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"Task executing");
    });
});
  • dispatch_sync with a global concurrent queue:
// Get the global concurrent queue
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// Add a task to the global concurrent queue for sync execution
dispatch_sync(queue, ^{
    NSLog(@"-----task---%@", [NSThread currentThread]);
});
  • dispatch_sync with a serial queue:
// Create a serial queue
dispatch_queue_t queue = dispatch_queue_create("cn.ibloodline.queue", NULL);
// Add a task to the serial queue for sync execution
dispatch_sync(queue, ^{
     NSLog(@"-----task---%@", [NSThread currentThread]);
});