iOS多线程之GCD

在讲iOS多线程之前,先讲讲线程和进程的概念;

进程 是一个应用程序在处理机上的一次执行过程,每个进程之间是独立的,每个进程均运行在其专用的且受保护的内存中;

线程 是指进程内的一个执行单元,也是进程内的可调度实体.(线程是进程中的一部分,进程包含多个线程在运行)

通俗的讲你可以把进程看作系统中正在运行的一个应用程序;线程则是在应用程序中执行的任务;
进程只能有一个,线程可以有多个;

区别:

  • 地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间.
  • 资源拥有:同一进程内的线程共享本进程的资源如内存,I/O,CPU等,但是进程之前的资源是独立的
  • 执行过程:每个独立的进程有一个程序运行的入口,顺序执行序列.但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制.
  • 一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉.所以多进程要比多线程健壮.
  • 线程是处理器调度的基本单元,但是进程不是.

GCD简介

Grand Central Dispatch 简称 GCD,是苹果开发中多线程操作的解决方案。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。

为什么使用GCD?

  • 可用于多核的并行运算
  • 自动利用 CPU 内核
  • 自动管理线程的生命周期
  • 开发只需要处理执行,不需要管理线程

GCD任务和队列

任务:就是执行操作,即可以执行的代码;执行任务有两种方式:同步异步

  • 同步(sync)
    阻塞线程: 同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行;
    不可以开辟新的线程
  • 异步(async)
    不会阻塞线程:异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务
    可以开辟新的线程

队列Dispatch Queue指执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,遵循 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务;

在 GCD 中有两种队列:串行队列并发队列。两者都符合 FIFO(先进先出)的原则。两者的主要区别是:执行顺序不同,以及开启线程数不同。

  • 串行队列(Serial Dispatch Queue): 每次只有一个任务被执行。让任务一个接着一个地执行

  • 并发队列(Concurrent Dispatch Queue): 可以让多个任务同时执行。(可以开启多个线程,并且同时执行任务)

GCD 的使用步骤

GCD的使用分为两步:

1. 创建队列
2. 将任务追加到队列中

创建队列

创建队列使用dispatch_queue_create函数;

/*
创建队列
 label:队列的唯一标识符
 attr:用来识别是串行队列还是并发队列;
      DISPATCH_QUEUE_SERIAL 表示串行队列;
      DISPATCH_QUEUE_CONCURRENT 表示并发队列;
*/ 
dispatch_queue_t
dispatch_queue_create(const char *_Nullable label,
        dispatch_queue_attr_t _Nullable attr);

获取队列

串行队列,GCD 默认提供了主队列(Main Dispatch Queue

dispatch_queue_t mainQueue = dispatch_get_main_queue();

并发队列,GCD 默认提供了全局并发队列(Global Dispatch Queue

/**
参数一表示队列优先级,默认 DISPATCH_QUEUE_PRIORITY_DEFAULT

DISPATCH_QUEUE_PRIORITY_DEFAULT
DISPATCH_QUEUE_PRIORITY_HIGH
DISPATCH_QUEUE_PRIORITY_DEFAULT
DISPATCH_QUEUE_PRIORITY_LOW
DISPATCH_QUEUE_PRIORITY_BACKGROUND

参数二暂时没用,用 0 即可
*/
 dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

创建任务

GCD提供了创建同步任务以及异步任务的函数;
同步任务: dispatch_sync

// 同步执行任务创建方法
dispatch_sync(queue, ^{
    // 这里放同步执行任务代码
});

异步任务: dispatch_async

// 异步执行任务创建方法
dispatch_async(queue, ^{
    // 这里放异步执行任务代码
});

任务和队列不同组合方式的区别

区别 串行队列 并发队列 主队列
sync 没有开启新线程,串行 没有开启新线程,串行 死锁卡住不执行
async 开启新线程,串行 开启新线程,并发 没有开启新线程,串行

GCD 的组合使用

串行队列 + 同步执行

不会开启新线程,在当前线程执行任务。执行完一个任务,再执行下一个任务。

/**
 * 串行队列 + 同步执行
 * 特点:不会开启新线程,在当前线程执行任务。执行完一个任务,再执行下一个任务。
 */
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
        // 追加任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
});
dispatch_sync(queue, ^{
        // 追加任务 2
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
});
dispatch_sync(queue, ^{
        // 追加任务 3
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
});
NSLog(@"syncSerial---end");

打印结果:
2021-06-23 23:38:38.990173+0800 GCD[6054:244903] 1---{number = 1, name = main}
2021-06-23 23:38:40.991133+0800 GCD[6054:244903] 2---{number = 1, name = main}
2021-06-23 23:38:42.992800+0800 GCD[6054:244903] 3---{number = 1, name = main}
2021-06-23 23:38:42.993152+0800 GCD[6054:244903] syncSerial---end

串行队列 + 异步执行

会开启新线程。执行完一个任务,再执行下一个任务。

/**
 * 串行队列 + 异步执行
 * 特点:会开启新线程。执行完一个任务,再执行下一个任务。
 */
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
        // 任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
});
dispatch_async(queue, ^{
        // 任务 2
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
});
dispatch_async(queue, ^{
        // 任务 3
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
});
NSLog(@"asyncSerial---end");
}

打印结果:
2021-06-23 23:40:34.958273+0800 GCD[6089:247194] asyncSerial---end
2021-06-23 23:40:36.961518+0800 GCD[6089:247325] 1---{number = 6, name = (null)}
2021-06-23 23:40:38.961931+0800 GCD[6089:247325] 2---{number = 6, name = (null)}
2021-06-23 23:40:40.965058+0800 GCD[6089:247325] 3---{number = 6, name = (null)}

并发队列 + 同步执行

不会开启新线程,在当前线程执行任务。执行完一个任务,再执行下一个任务。

/**
* 并发队列 + 同步执行
* 特点:在当前线程中执行任务,不会开启新线程,执行完一个任务,再执行下一个任务。
*/
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
       // 任务 1
       [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
       NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
});
dispatch_sync(queue, ^{
       // 任务 2
       [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
       NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
});
dispatch_sync(queue, ^{
       // 任务 3
       [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
       NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
});
NSLog(@"syncConcurrent---end");

打印结果:
2021-06-23 23:25:11.536374+0800 GCD[5965:232139] 1---{number = 1, name = main}
2021-06-23 23:25:13.536829+0800 GCD[5965:232139] 2---{number = 1, name = main}
2021-06-23 23:25:15.537384+0800 GCD[5965:232139] 3---{number = 1, name = main}
2021-06-23 23:25:15.537553+0800 GCD[5965:232139] syncConcurrent---end

并发队列 + 异步执行

可以开启多个线程,任务同时执行。

/**
 * 并发队列 + 异步执行
 * 特点:可以开启多个线程,任务同时执行。
 */
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
        // 任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
});
dispatch_async(queue, ^{
        // 任务 2
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
});
dispatch_async(queue, ^{
        // 任务 3
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
});
NSLog(@"asyncConcurrent---end");

打印结果:
2021-06-23 23:36:45.144759+0800 GCD[6036:243090] asyncConcurrent---end
2021-06-23 23:36:47.147338+0800 GCD[6036:243216] 3---{number = 6, name = (null)}
2021-06-23 23:36:47.147338+0800 GCD[6036:243212] 2---{number = 5, name = (null)}
2021-06-23 23:36:47.147340+0800 GCD[6036:243210] 1---{number = 3, name = (null)}

GCD 的其他方法

GCD栅栏方法: dispatch_barrier_async

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

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

打印结果:
2021-06-23 23:47:54.735553+0800 GCD[6119:255683] 1---{number = 5, name = (null)}
2021-06-23 23:47:54.735569+0800 GCD[6119:255682] 2---{number = 8, name = (null)}
2021-06-23 23:47:56.740711+0800 GCD[6119:255682] barrier---{number = 8, name = (null)}
2021-06-23 23:47:58.743837+0800 GCD[6119:255683] 4---{number = 5, name = (null)}
2021-06-23 23:47:58.743837+0800 GCD[6119:255682] 3---{number = 8, name = (null)}

GCD 延时执行方法:dispatch_after

dispatch_after 方法并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中。

void
dispatch_after(dispatch_time_t when, dispatch_queue_t queue,
        dispatch_block_t block);
/**
 * 延时执行方法 dispatch_after
 */
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    // 2.0 秒后异步追加任务代码到主队列,并开始执行
    NSLog(@"after---%@",[NSThread currentThread]);  // 打印当前线程
);

GCD 执行一次:dispatch_once

在创建单例、或者有整个程序运行过程中只执行一次的代码时,我们就用到了 GCD 的 dispatch_once 方法。使用 dispatch_once 方法能保证某段代码在程序运行过程中只被执行 1 次,并且即使在多线程的环境下,dispatch_once 也可以保证线程安全

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

GCD 队列组:dispatch_group

场景:当需要异步执行多个耗时任务,然后执行完毕后回到主线程;

  • dispatch_group_notify: 监听 group 中任务的完成状态,当所有的任务都执行完成后,追加任务到 group 中,并执行任务;
dispatch_group_t group =  dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 追加任务 1
    [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
    NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
});
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 追加任务 2
    [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
    NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    // 等前面的异步任务 1、任务 2 都执行完毕后,回到主线程执行下边任务
   [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
   NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
   NSLog(@"group---end");
});
  • dispatch_group_wait: 暂停当前线程(阻塞当前线程),等待指定的 group 中的任务执行完成后,才会往下继续执行
dispatch_group_t group =  dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 追加任务 1
    [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
    NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
});
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 追加任务 2
    [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
    NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
});
// 等待上面的任务全部完成后,会往下继续执行(会阻塞当前线程)
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

NSLog(@"group---end");
  • dispatch_group_enter: 标志着一个任务追加到 group,执行一次,相当于 group 中未执行完毕任务数 +1;
void
dispatch_group_enter(dispatch_group_t group);
  • dispatch_group_leave: 标志着一个任务离开了 group,执行一次,相当于 group 中未执行完毕任务数 -1;
void
dispatch_group_leave(dispatch_group_t group);

当 group 中未执行完毕任务数为0的时候,才会使 dispatch_group_wait 解除阻塞,以及执行追加到 dispatch_group_notify 中的任务;

/**
 * 队列组 dispatch_group_enter、dispatch_group_leave
 */
- (void)groupEnterAndLeave {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"group---begin");
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        // 追加任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程

        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        // 追加任务 2
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        
        dispatch_group_leave(group);
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的异步操作都执行完毕后,回到主线程.
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
    
        NSLog(@"group---end");
    });
}

GCD 信号量:dispatch_semaphore

信号量是基于计数器的一种多线程同步机制,用来管理对资源的并发访问。
GCD 中的信号量是指持有计数的信号。在 Dispatch Semaphore 中,使用计数来完成这个功能,信号量为0则阻塞线程,大于0则不会阻塞。则我们通过改变信号量的值,来控制是否阻塞线程,从而达到线程同步。
Dispatch Semaphore 提供了三个方法:

  • dispatch_semaphore_create:创建一个 Semaphore 并初始化信号的总量
  • dispatch_semaphore_signal:发送一个信号,让信号总量加 1
  • dispatch_semaphore_wait:可以使总信号量减 1,信号总量小于 0 时就会一直等待(阻塞所在线程),否则就可以正常执行。

信号量dispatch_semaphore主要用于两个方面:保持线程同步、为线程加锁

  • 保持线程同步
/** 以AFNetworking源码为例*/ 
- (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"];
        }
        // 提高信号量, 使信号量加1,并返回
        dispatch_semaphore_signal(semaphore);
    }];
    // 等待降低信号量
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    return tasks;
}
  • 为线程加锁
- (void)test {
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    for (int i = 0; i < 10; i++) {
         dispatch_async(queue, ^{
              // 相当于加锁
              dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
              /*
               todo:
               */
              // 相当于解锁
              dispatch_semaphore_signal(semaphore);
          });
    }
}

你可能感兴趣的:(iOS多线程之GCD)