Grand Central Dispatch(GCD) 是 Apple 开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并发任务。在 Mac OS X 10.6 雪豹中首次推出,也可在 iOS 4 及以上版本使用。

GCD 有很多好处

NSOperation 实现多线程的使用步骤分为三步:

GCD 中两个核心概念:任务和队列

同步执行(sync)和异步执行(async)。两者的主要区别是:

使用主队列,在主线程中调用asyncMain

- (void)asyncMain {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"asyncMain---begin");
    // 使用主队列
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    // dispatch_async 1.是否会阻塞线程, 2.是否具备开启线程的能力,不一定开启,说的是能力
    dispatch_async(queue, ^{
        // 追加任务1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    
    NSLog(@"asyncMain---end");
}

输出结果:

currentThread---<NSThread: 0x6000001b2900>{number = 1, name = main}
asyncMain---begin
asyncMain---end
1---<NSThread: 0x6000001b2900>{number = 1, name = main}
1---<NSThread: 0x6000001b2900>{number = 1, name = main}

注意,这个方法是在主线程中调用的,使用了主队列,然而并没有按照代码顺序执行,因为是异步函数,不会阻塞线程

如果是主队列,主线程下,使用同步函数呢?是不是会按照代码顺序执行?

示例代码如下:

- (void)syncMainQueue {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"asyncMain---begin");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    // dispatch_async 1.是否会阻塞线程, 2.是否具备开启线程的能力,不一定开启,说的是能力
    dispatch_sync(queue, ^{
        // 追加任务0
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"0---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    NSLog(@"asyncMain---end");
}

输出结果,是崩溃

currentThread---<NSThread: 0x6000034d6940>{number = 1, name = main}
asyncMain---begin
(lldb) 

这里就是死锁问题:进入互相等待,你等我,我等你,谁都不相让.在主线程中,同步函数会放到主队列中,同步函数的任务也放到主队列中,按照队列定义:FIFO,同步函数要先出去,同步函数又要求先执行任务.任务要是先执行,必须要等同步函数,所以就进入互相等待中

那么以上代码,如果是异步线程下,主队列,使用同步函数呢?是不是也是发生死锁?

[NSThread detachNewThreadSelector:@selector(syncMainQueue) toTarget:self withObject:nil];

输出结果:

currentThread---<NSThread: 0x600001aa4d00>{number = 3, name = (null)}
asyncMain---begin
0---<NSThread: 0x600001ac5400>{number = 1, name = main}
0---<NSThread: 0x600001ac5400>{number = 1, name = main}
asyncMain---end

可以看到

主队列是串行队列,每次只有一个任务被执行,任务一个接一个按顺序执行

GCD 栅栏方法:dispatch_barrier_async

dispatch_barrier_async函数会等待前边追加到并发队列中的任务全部执行完毕之后,再将指定的任务追加到该异步队列中。然后在dispatch_barrier_async函数追加的任务执行完毕之后,异步队列才恢复为一般动作,接着追加任务到该异步队列并开始执行

示例

- (void)barrier {
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        // 追加任务1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    dispatch_async(queue, ^{
        // 追加任务2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    
    
    
    dispatch_barrier_async(queue, ^{
        // 追加任务 barrier
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
        }
    });
    
    dispatch_async(queue, ^{
        // 追加任务3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    dispatch_async(queue, ^{
        // 追加任务4
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"4---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
}

输出结果:

2---<NSThread: 0x600002e5e680>{number = 4, name = (null)}
1---<NSThread: 0x600002e69f00>{number = 3, name = (null)}
1---<NSThread: 0x600002e69f00>{number = 3, name = (null)}
2---<NSThread: 0x600002e5e680>{number = 4, name = (null)}
barrier---<NSThread: 0x600002e69f00>{number = 3, name = (null)}
barrier---<NSThread: 0x600002e69f00>{number = 3, name = (null)}
4---<NSThread: 0x600002e5e680>{number = 4, name = (null)}
3---<NSThread: 0x600002e69f00>{number = 3, name = (null)}
4---<NSThread: 0x600002e5e680>{number = 4, name = (null)}
3---<NSThread: 0x600002e69f00>{number = 3, name = (null)}

可以清晰的看到,栅栏函数就是分割作用,先执行栅栏之前的函数,然后栅栏,之后再处理追加的任务

### GCD 延时执行方法:dispatch_after

 // 前线程是主线程
 NSLog(@"dispatch_after---begin");
 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    // 2.0秒后异步追加任务代码到主队列,并开始执行
    NSLog(@"after---%@",[NSThread currentThread]);  // 打印当前线程
});
NSLog(@"dispatch_after---end");

打印输出

dispatch_after---begin
dispatch_after---end
after---<NSThread: 0x600002f3aac0>{number = 1, name = main}

GCD 一次性代码(只执行一次):dispatch_once

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    // 只执行1次的代码(这里面默认是线程安全的)
});

GCD 快速迭代方法:dispatch_apply

1.主线程下,串行队列和普通的for循环一个效果

// 主线程下 串行队列
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
dispatch_apply(6, queue, ^(size_t index) {
    NSLog(@"%zd---%@",index, [NSThread currentThread]);
});
NSLog(@"apply---end");

打印输出

0---<NSThread: 0x600000562940>{number = 1, name = main}
1---<NSThread: 0x600000562940>{number = 1, name = main}
2---<NSThread: 0x600000562940>{number = 1, name = main}
3---<NSThread: 0x600000562940>{number = 1, name = main}
4---<NSThread: 0x600000562940>{number = 1, name = main}
5---<NSThread: 0x600000562940>{number = 1, name = main}
apply---end

2.主线程下,主队列情况下会发生什么?

 // 主线程下,主队列
 dispatch_queue_t queue = dispatch_get_main_queue();
 dispatch_apply(6, queue, ^(size_t index) {
    NSLog(@"%zd---%@",index, [NSThread currentThread]);
 });
 NSLog(@"apply---end");

发生崩溃,如果没猜错,发生了死锁:主线程下,主队列,进行了同步操作

3.主线程下,并发队列情况下会发生什么?

 // 主线程下,并发队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_apply(6, queue, ^(size_t index) {
    NSLog(@"%zd---%@",index, [NSThread currentThread]);
});
NSLog(@"apply---end");

输出打印

1---<NSThread: 0x600003e93040>{number = 3, name = (null)}
0---<NSThread: 0x600003ece940>{number = 1, name = main}
2---<NSThread: 0x600003ea1500>{number = 4, name = (null)}
3---<NSThread: 0x600003e9d280>{number = 5, name = (null)}
5---<NSThread: 0x600003e93040>{number = 3, name = (null)}
4---<NSThread: 0x600003ece940>{number = 1, name = main}
apply---end

dispatch_apply 可以 在多个线程中同时(异步)遍历多个数字

总结一点:无论是串行队列,还是并发队列,都是要等待全部任务执行完毕,也就是说这个遍历会阻塞当前线程

GCD 队列组:dispatch_group和dispatch_group_notify

需求:分别异步执行2个耗时任务,然后当2个耗时任务都执行完毕后再回到主线程执行任务。 这时候我们可以用到 GCD 的队列组

// 队列组
- (void)groupNotify {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"group---begin");
    
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任务1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任务2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        }
        dispatch_group_leave(group);
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的异步任务1、任务2都执行完毕后,回到主线程执行下边任务
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
        }
        NSLog(@"group---end");
    });
}

打印输出

currentThread---<NSThread: 0x6000028a5400>{number = 1, name = main}
group---begin
2---<NSThread: 0x6000028c2900>{number = 3, name = (null)}
1---<NSThread: 0x6000028c9b00>{number = 4, name = (null)}
2---<NSThread: 0x6000028c2900>{number = 3, name = (null)}
1---<NSThread: 0x6000028c9b00>{number = 4, name = (null)}

3---<NSThread: 0x6000028a5400>{number = 1, name = main}
3---<NSThread: 0x6000028a5400>{number = 1, name = main}
group---end

调用队列组的 dispatch_group_notify 回到指定线程执行任务

实际项目中,一个小页面可能有两个接口,需要两个接口返回数据合并起来才可以刷新界面,这里就可以用到队列组,示例:

dispatch_group_t group = dispatch_group_create();

// A网络请求
dispatch_group_enter(group);
[self sendRequestWithCompletion:^(id response) {
    ...
    dispatch_group_leave(group);
}];

// B网络请求
dispatch_group_enter(group);
[self sendRequestWithCompletion:^(id response) {
    ...
    dispatch_group_leave(group);
}];

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    ...
});

Dispatch Semaphore 线程同步

需求:异步执行耗时任务,并使用异步执行的结果进行一些额外的操作,换句话说,我们要使用异步执行后的结果比如一个数值,然后拿到这个值后进行相应的处理,一般来说,我们在异步回调里面进行处理

代码示例如下

__block int number = 0;
dispatch_async(queue, ^{
    // 追加任务1
    [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
    NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    
    number = 100;
    // 伪代码,一般要回到主线程进行block回调
    testBlock(number)
});

需求:不使用回调,监听异步任务的结束,结束后发个通知,再执行之后的代码,这个时候就可以使用信号量了

- (void)semaphoreSync {
    
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"semaphore---begin");
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    
    __block int number = 0;
    dispatch_async(queue, ^{
        // 追加任务1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        
        number = 100;
        dispatch_semaphore_signal(semaphore);
    });
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"semaphore---end,number = %d",number);
    NSLog(@"currentThread---%@",[NSThread currentThread]);
}

打印输出:

currentThread---<NSThread: 0x600002fb1400>{number = 1, name = main}
semaphore---begin
1---<NSThread: 0x600002fd7c40>{number = 3, name = (null)}
semaphore---end,number = 100
currentThread---<NSThread: 0x600002fb1400>{number = 1, name = main}

使用信号量的dispatch_semaphore_wait方法,可以阻塞当前线程(主线程),当信号量 semaphore == 1的时候,就会发出通知,告诉dispatch_semaphore_wait不要阻塞了,可以继续执行下面的代码了

这样就实现了线程同步,将异步执行任务转换为同步执行任务

这样子感觉有点绕,干脆直接主线程同步方法,按顺序执行不就完事了吗?搞这么复杂干嘛?

年轻人too young too simple

异步操作是不会阻塞主线程的,你在主线程中处理这些耗时任务,如果是App不就卡死在这个界面了?

且看AFNetworking 中 AFURLSessionManager.m 里面的 tasksForKeyPath: 方法。通过引入信号量的方式,等待异步执行任务结果,获取到 tasks,然后再返回该 tasks

- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
    __block NSArray *tasks = nil;
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
        if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
            tasks = dataTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
            tasks = uploadTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
            tasks = downloadTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
            tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
        }

        dispatch_semaphore_signal(semaphore);
    }];

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    return tasks;
}

线程安全(使用 semaphore 加锁)

/**
* 线程安全:使用 semaphore 加锁
* 初始化火车票数量、卖票窗口(线程安全)、并开始卖票
*/
- (void)initTicketStatusSave {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"semaphore---begin");
    
    semaphoreLock = dispatch_semaphore_create(1);
    
    self.ticketSurplusCount = 50;
    
    // queue1 代表北京火车票售卖窗口
    dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
    // queue2 代表上海火车票售卖窗口
    dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
    
    __weak typeof(self) weakSelf = self;
    dispatch_async(queue1, ^{
        [weakSelf saleTicketSafe];
    });
    
    dispatch_async(queue2, ^{
        [weakSelf saleTicketSafe];
    });
}

/**
 * 售卖火车票(线程安全)
 */
- (void)saleTicketSafe {
    while (1) {
        // 相当于加锁
        dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);
        
        if (self.ticketSurplusCount > 0) {  //如果还有票,继续售卖
            self.ticketSurplusCount--;
            NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
        } else { //如果已卖完,关闭售票窗口
            NSLog(@"所有火车票均已售完");
            
            // 相当于解锁
            dispatch_semaphore_signal(semaphoreLock);
            break;
        }
        
        // 相当于解锁
        dispatch_semaphore_signal(semaphoreLock);
    }
}