The GCD multithreading

Synchronous and asynchronous tasks

The difference between the two is:

  • A synchronization task returns only after the task is complete
  • An asynchronous task returns directly without waiting for the completion of the task

It’s also described in Apple’s official documentation

Synchronization task

Submits a block object for execution and returns after that block finishes executing.

Asynchronous tasks

Submits a block for asynchronous execution on a dispatch queue and returns immediately.

Serial queue, concurrent queue

The difference between the two is:

  • Tasks in a serial queue are executed sequentially, followed by the next task after the previous task is executed, following the FIFO (first in, first out) principle
  • Tasks in a concurrent queue can be executed concurrently.

Two tasks and two queues are used in combination

Task/queue Serial queues Concurrent queue Main queue (serial queue)
synchronous Child threads will not be started and tasks will be executed sequentially (adding a synchronization task nested in the current queue will cause deadlock) Child threads are not started and tasks are executed sequentially Main thread calls cause deadlocks, other thread calls do not
asynchronous Child threads are opened and tasks are executed sequentially Child threads are started and tasks are executed concurrently Child threads are not started and tasks are executed serially on the main thread.

Synchronous + serial

- (void)syncSerrialTask {
    // Synchronization task + serial queue
    dispatch_queue_t queue = dispatch_queue_create("com.thatisawesome.demo", DISPATCH_QUEUE_SERIAL);
    
    [self runTaskWithNum:1];
    
    dispatch_sync(queue, ^{
        [self runTaskWithNum:2];
    });
    
    dispatch_sync(queue, ^{
        [self runTaskWithNum:3];
    });
    
    dispatch_sync(queue, ^{
        [self runTaskWithNum:4];
    });
    
    [self runTaskWithNum:5];
}


- (void)runTaskWithNum:(NSUInteger)num {
    NSLog(@"start execute task %lu on thread %@", num, [NSThread currentThread]);
    sleep(2);
    NSLog(@"end execute task %lu on thread %@", num, [NSThread currentThread]);
}
Copy the code

Running results:

2022-01-11 14:07:54.137625+0800 Demo[46700:394383] Start execute task 1 on thread <NSThread: 0x60000192c600>{number = 1, Name = main} 2022-01-11 14:07:56.139009+0800 Demo[467000:394383] End execute task 1 on thread <NSThread: 0x60000192c600>{number = 1, Name = main} 2022-01-11 14:07:56.139288+0800 Demo[467000:394383] Start execute task 2 on thread <NSThread: 0x60000192c600>{number = 1, Name = main} 2022-01-11 14:07:58.140514+0800 Demo[467000:394383] end execute task 2 on thread <NSThread: 0x60000192c600>{number = 1, Name = main} 2022-01-11 14:07:58.140797+0800 Demo[467000:394383] Start execute task 3 on thread <NSThread: 0x60000192c600>{number = 1, Name = main} 2022-01-11 14:08:00.141964+0800 Demo[467000:394383] End execute task 3 on thread <NSThread: 0x60000192c600>{number = 1, Name = main} 2022-01-11 14:08:00.142212+0800 Demo[467000:394383] Start execute task 4 on thread <NSThread: 0x60000192c600>{number = 1, Name = main} 2022-01-11 14:08:02.143410+0800 Demo[467000:394383] End execute task 4 on thread <NSThread: 0x60000192c600>{number = 1, Name = main} 2022-01-11 14:08:02.143661+0800 Demo[467000:394383] Start execute task 5 on thread <NSThread: 0x60000192c600>{number = 1, Name = main} 2022-01-11 14:08:04.144890+0800 Demo[467000:394383] End execute task 5 on thread <NSThread: 0x60000192c600>{number = 1, name = main}Copy the code

As you can see from the result, the task is executed sequentially without starting a new thread.

Synchronization + concurrency

- (void)syncConcurrentTask {
    dispatch_queue_t queue = dispatch_queue_create("com.thatisawesome.demo", DISPATCH_QUEUE_CONCURRENT);
    
    [self runTaskWithNum:1];
    
    dispatch_sync(queue, ^{
        [self runTaskWithNum:2];
    });
    
    dispatch_sync(queue, ^{
        [self runTaskWithNum:3];
    });
    
    dispatch_sync(queue, ^{
        [self runTaskWithNum:4];
    });
    
    [self runTaskWithNum:5];
}
Copy the code

Running results:

2022-01-11 14:11:42.240424+0800 Demo[46833:397762] start execute task 1 on thread <NSThread: 0x600002af0700>{number = 1, name = main}
2022-01-11 14:11:44.241164+0800 Demo[46833:397762] end   execute task 1 on thread <NSThread: 0x600002af0700>{number = 1, name = main}
2022-01-11 14:11:44.241326+0800 Demo[46833:397762] start execute task 2 on thread <NSThread: 0x600002af0700>{number = 1, name = main}
2022-01-11 14:11:46.242443+0800 Demo[46833:397762] end   execute task 2 on thread <NSThread: 0x600002af0700>{number = 1, name = main}
2022-01-11 14:11:46.242588+0800 Demo[46833:397762] start execute task 3 on thread <NSThread: 0x600002af0700>{number = 1, name = main}
2022-01-11 14:11:48.243767+0800 Demo[46833:397762] end   execute task 3 on thread <NSThread: 0x600002af0700>{number = 1, name = main}
2022-01-11 14:11:48.244037+0800 Demo[46833:397762] start execute task 4 on thread <NSThread: 0x600002af0700>{number = 1, name = main}
2022-01-11 14:11:50.245273+0800 Demo[46833:397762] end   execute task 4 on thread <NSThread: 0x600002af0700>{number = 1, name = main}
2022-01-11 14:11:50.245550+0800 Demo[46833:397762] start execute task 5 on thread <NSThread: 0x600002af0700>{number = 1, name = main}
2022-01-11 14:11:52.246746+0800 Demo[46833:397762] end   execute task 5 on thread <NSThread: 0x600002af0700>{number = 1, name = main}
Copy the code

The task is executed serially without starting a new thread.

Asynchronous + serial

- (void)asyncSerrialTask {
    dispatch_queue_t queue = dispatch_queue_create("com.thatisawesome.demo", DISPATCH_QUEUE_SERIAL);
    
    [self runTaskWithNum:1];
    
    dispatch_async(queue, ^{
        [self runTaskWithNum:2];
    });
    
    dispatch_async(queue, ^{
        [self runTaskWithNum:3];
    });
    
    dispatch_async(queue, ^{
        [self runTaskWithNum:4];
    });
    
    [self runTaskWithNum:5];
}
Copy the code

Running results:

2022-01-11 14:15:01.846326+0800 Demo[46949:400738] start execute task 1 on thread <NSThread: 0x60000204c180>{number = 1, name = main}
2022-01-11 14:15:03.847623+0800 Demo[46949:400738] end   execute task 1 on thread <NSThread: 0x60000204c180>{number = 1, name = main}
2022-01-11 14:15:03.847914+0800 Demo[46949:400738] start execute task 5 on thread <NSThread: 0x60000204c180>{number = 1, name = main}
2022-01-11 14:15:03.847950+0800 Demo[46949:400839] start execute task 2 on thread <NSThread: 0x600002031f80>{number = 3, name = (null)}
2022-01-11 14:15:05.849427+0800 Demo[46949:400738] end   execute task 5 on thread <NSThread: 0x60000204c180>{number = 1, name = main}
2022-01-11 14:15:05.851383+0800 Demo[46949:400839] end   execute task 2 on thread <NSThread: 0x600002031f80>{number = 3, name = (null)}
2022-01-11 14:15:05.851655+0800 Demo[46949:400839] start execute task 3 on thread <NSThread: 0x600002031f80>{number = 3, name = (null)}
2022-01-11 14:15:07.856812+0800 Demo[46949:400839] end   execute task 3 on thread <NSThread: 0x600002031f80>{number = 3, name = (null)}
2022-01-11 14:15:07.856949+0800 Demo[46949:400839] start execute task 4 on thread <NSThread: 0x600002031f80>{number = 3, name = (null)}
2022-01-11 14:15:09.862166+0800 Demo[46949:400839] end   execute task 4 on thread <NSThread: 0x600002031f80>{number = 3, name = (null)}
Copy the code

Tasks 1,5 are executed serially on the main thread, and tasks 2, 3, and 4 are executed serially on child threads. You can see from the result that a new thread was created.

Asynchronous + concurrent

- (void)asyncConcurrentTask {
    dispatch_queue_t queue = dispatch_queue_create("com.thatisawesome.demo", DISPATCH_QUEUE_CONCURRENT);
    
    [self runTaskWithNum:1];
    
    dispatch_async(queue, ^{
        [self runTaskWithNum:2];
    });
    
    dispatch_async(queue, ^{
        [self runTaskWithNum:3];
    });
    
    dispatch_async(queue, ^{
        [self runTaskWithNum:4];
    });
    
    [self runTaskWithNum:5];
}
Copy the code

Running results:

2022-01-11 14:20:19.425785+0800 Demo[47168:405302] start execute task 1 on thread <NSThread: 0x6000020384c0>{number = 1, name = main}
2022-01-11 14:20:21.426931+0800 Demo[47168:405302] end   execute task 1 on thread <NSThread: 0x6000020384c0>{number = 1, name = main}
2022-01-11 14:20:21.427064+0800 Demo[47168:405302] start execute task 5 on thread <NSThread: 0x6000020384c0>{number = 1, name = main}
2022-01-11 14:20:21.427119+0800 Demo[47168:405392] start execute task 2 on thread <NSThread: 0x60000205b440>{number = 5, name = (null)}
2022-01-11 14:20:21.427143+0800 Demo[47168:405394] start execute task 4 on thread <NSThread: 0x600002069540>{number = 3, name = (null)}
2022-01-11 14:20:21.427142+0800 Demo[47168:405395] start execute task 3 on thread <NSThread: 0x60000207e380>{number = 4, name = (null)}
2022-01-11 14:20:23.428279+0800 Demo[47168:405302] end   execute task 5 on thread <NSThread: 0x6000020384c0>{number = 1, name = main}
2022-01-11 14:20:23.431917+0800 Demo[47168:405394] end   execute task 4 on thread <NSThread: 0x600002069540>{number = 3, name = (null)}
2022-01-11 14:20:23.432210+0800 Demo[47168:405392] end   execute task 2 on thread <NSThread: 0x60000205b440>{number = 5, name = (null)}
2022-01-11 14:20:23.432252+0800 Demo[47168:405395] end   execute task 3 on thread <NSThread: 0x60000207e380>{number = 4, name = (null)}
Copy the code

Tasks 1,5 are executed serially on the main thread, and tasks 2, 3, and 4 are executed concurrently on child threads. You can see from the result that a new thread was created.

The current serial queue is locked because it is nested with synchronization tasks

- (void)syncSerrialTask {
    // Synchronization task + serial queue
    dispatch_queue_t queue = dispatch_queue_create("com.thatisawesome.demo", DISPATCH_QUEUE_SERIAL);
    
    [self runTaskWithNum:1];
    
    dispatch_sync(queue, ^{
        [self runTaskWithNum:2];
    });
    
    dispatch_sync(queue, ^{
        [self runTaskWithNum:3];
        dispatch_sync(queue, ^{
            [self runTaskWithNum:4];
        });
    });
    
    [self runTaskWithNum:5];
}
Copy the code

Running results:

Cause a deadlock and crash at runtime.

Deadlock cause analysis

in

dispatch_sync(queue, ^{
        [self runTaskWithNum:3];
        dispatch_sync(queue, ^{
            [self runTaskWithNum:4];
        });
    });
Copy the code

In the code, tasks 3 and 4 are synchronously added to the serial queue as a whole, and then task 4 is synchronously added to the serial queue, resulting in: If task 4 wants to be executed, tasks 3 and 4 added to the queue as a whole must be executed first. However, tasks 3 and 4 as a whole also contain one task 4. As a result, they wait for each other, resulting in deadlock.

A deadlock is also triggered by a call to synchronization + main queue in the main thread

The reason is that the tasks of the main thread are placed in the main queue, which is also a serial queue. As in the example in this article, synchronization tasks are nested into the current queue, resulting in deadlock.

Thread synchronization

Suppose we now have N apples, two monkeys, one monkey eats 2 apples at a time, one monkey eats 3 apples at a time, both monkeys eat at the same time, until there are not enough apples left for each monkey to eat, how to simulate this process with multiple threads. This is the author in the interview ali encountered a pen test.

Don’t worry about thread safety

@interface MonkeyEatApple (a)

@property (nonatomic, assign) NSInteger appleCount;
@property (nonatomic, strong) dispatch_semaphore_t semaphore;

@end

@implementation MonkeyEatApple

- (instancetype)initWithAppleCount:(NSInteger)appleCount {
    if (self = [super init]) {
        _appleCount = appleCount;
    }
    return self;
}

- (void)startEat {
    
    dispatch_queue_t queue1 = dispatch_queue_create("club.thatisawesome.queue1", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue2 = dispatch_queue_create("club.thatisawesome.queue2", DISPATCH_QUEUE_SERIAL);
    
    self.semaphore = dispatch_semaphore_create(1);
    
    __weak typeof(self) weakSelf = self;
    dispatch_async(queue1, ^{
        [weakSelf eatAppleWithCount:2];
    });
    
    dispatch_async(queue2, ^{
        [weakSelf eatAppleWithCount:3];
    });
}

- (void)eatAppleWithCount:(NSInteger)appleCount {
    while (YES) {
        NSString *monkeyStr = @"A";
        if (appleCount == 2) {
            monkeyStr = @"B";
        }
        if (self.appleCount >= 2) {
            [NSThread sleepForTimeInterval:0.5];
            self.appleCount -= appleCount;
            NSLog(@"%@ monkey ate %ld apples, there are %ld apples left", monkeyStr, appleCount, self.appleCount);
        } else {
NSLog(@"%@ wants to eat, but there's no more to eat.", monkeyStr);
            break;
        }
    }
}

@end
Copy the code

The output

[93179:739398] B Monkey eats 2 apples, [93179:739397] A Monkey eats three apples. [93179:739398] B Monkey eats two apples, 2022-01-12 23:04:05.419167+0800 Demo[93179:739397] A Monkey eats three apples, 2022-01-12 23:04:05.923410+0800 Demo[93179:739397] 2022-01-12 23:04:05.923410+0800 Demo[93179:739398] B Monkey eats two apples, [93179:739398] B Monkey eats two apples, 2022-01-12 23:04:06.428653+0800 Demo[93179:739397] A Monkey eats three apples, 2022-01-12 23:04:06.933991+0800 Demo[93179:739398] B Monkey eats two apples, 2022-01-12 23:04:06.933991+0800 Demo[93179:739397] A Monkey eats three apples, 2022-01-12 23:04:06.934244+0800 Demo[93179:739398] A want to eat, 2022-01-12 23:04:06.934282+0800 Demo[93179:739397] B: I want to eat, but I don't have any foodCopy the code

From the output, it can be seen that without considering thread synchronization, the data will be distorted. There are 15 apples in total. Monkey B ate 2 apples, but there are 13 apples left.

Here I set the total number of apples to 6 and observe the output

[95516:779892] A Monkey eats three apples, 2022-01-13 00:03:07.904433+0800 Demo[95516:779890] B Monkey eats two apples, 2022-01-13 00:03:08.409648+0800 Demo[95516:779890] B Monkey eats two apples, [95516:779892] A Monkey eats three apples. 2022-01-13 00:03:08.409862+0800 Demo[95516:779892] A want to eat, 2022-01-13 00:03:08.409860+0800 Demo[95516:779890] B: I want to eat, but I don't have any moreCopy the code

Above is a detailed illustration of the output. It is clear that the data is corrupted when the thread is not synchronized.

Use semaphores for thread synchronization

- (void)eatAppleWithCount:(NSInteger)appleCount {
    while (YES) {
        dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
        NSString *monkeyStr = @"A";
        if (appleCount == 2) {
            monkeyStr = @"B";
        }
        if (self.appleCount >= 2) {
            [NSThread sleepForTimeInterval:0.5];
            self.appleCount -= appleCount;
            NSLog(@"%@ monkey ate %ld apples, there are %ld apples left", monkeyStr, appleCount, self.appleCount);
        } else {
            dispatch_semaphore_signal(self.semaphore);
            NSLog(@"%@ wants to eat, but there's no more to eat.", monkeyStr);
            break;
        }
        dispatch_semaphore_signal(self.semaphore); }}Copy the code

The output

[96939:804913] B The monkey ate two apples, 2022-01-13 00:39:29.475415+0800 Demo[96939:804914] A Monkey eats three apples, 2022-01-13 00:39:29.475550+0800 Demo[96939:804913] B want to eat, 2022-01-13 00:39:29.475571+0800 Demo[96939:804914] A: I want to eat, but I don't have to eatCopy the code

First post in personal blog click jump