iOS基础 - GCD:同步 | 异步 | 串行 | 并发

▶ 前言

GCD 全称 Grand Central Dispatch,是纯 C 编写,提供了多且强大的函数。在编写 GCD 相关代码的时我们要记住面对的是函数,而不是方法!GCD 存在于 libdispatch.dylib 这个库中,这个调度库包含了 GCD 的所有的东西,任何 iOS 程序默认就加载了这个库

注:GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程),程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码

▶ 队列

有两种任务派发方式

// 同步执行:完成了它预定的任务后才返回,阻塞当前线程
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
// 异步执行:会立即返回,预定的任务会完成但不会等它完成,不阻塞当前线程
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

A. 有两种队列:Serial Dispatch Queue 和 Concurrent Dispatch Queue !遵循 FIFO 原则:先进先出,后进后出

串行队列:一次只执行一个任务,它通常用于同步访问特定的资源或数据。当你创建多个串行队列时它们各自内部是同步进行的,但队列和队列之间是并发执行的。获得串行有两种途径

// 方式一:使用 dispatch_queue_create
dispatch_queue_create(const char *label,  dispatch_queue_attr_t attr);
// 方式二:使用主队列,它是跟主线程相关联的队列
dispatch_get_main_queue();

B. 并发队列:可以并发的执行多个任务,并发功能只有在异步函数下才有效。GCD 提供了全局并发队列用来供整个应用使用,不需要手动创建

// 全局队列
dispatch_queue_t queue = dispatch_get_global_queue(dispatch_queue_priority_t priority,unsigned long flags);
// 全局并发队列的优先级
#define DISPATCH_QUEUE_PRIORITY_HIGH 2    //
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 中(默认)
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)  //
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后台

主队列 | 全局队列 | 自定义队列

- (void)viewDidLoad {
    [super viewDidLoad];
    // 主队列: 是一个单例,意味着在不同地方获取均为同一个
    // 它的执行方式是串行,其任务都是运行在主线程
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    NSLog(@"%@",mainQueue);

    // 全局队列: 并发执行,任务都是运行在子线程
    dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
    NSLog(@"%@",globalQueue);

    // 自定义队列:运行在子线程,任务执行可以指定是串行还是并发,默认串行
    // DISPATCH_QUEUE_CONCURRENT
    // DISPATCH_QUEUE_SERIAL
    dispatch_queue_t customQueue = dispatch_queue_create("Hello GCD", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"%@",customQueue);
}

▶ 异步函数 | 同步函数

这里我们使用异步函数,往并发队列中添加任务,先来感受下实际效果

 1 -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
 2     
 3     // BlockA
 4     dispatch_async(dispatch_get_global_queue(0, 0), ^{
 5 
 6         NSLog(@"异步+并发 = %@",[NSThread currentThread]);
 7         
 8         // 测试一:不会阻塞UI
 9         // [self performSelectorInBackground:@selector(crazyTextA) withObject:nil]; // 在新线程中执行
10         // [self performSelectorOnMainThread:@selector(updateUI) withObject:self waitUntilDone:YES];
11         
12         //-----------------------
13         
14         // 测试二:会阻塞更新UI
15         [self crazyTextA]; // 要等待 crazyTextA 执行完后才能更新UI
16         [self performSelectorOnMainThread:@selector(updateUI) withObject:self waitUntilDone:YES];
17     });
18 
19     // 不须等待 BlockA 中的代码执行完毕
20     NSLog(@"hello Block %@",[NSThread currentThread]);
21 
22     // BlockB:同样不须等待 BlockA 中的代码执行完毕
23     dispatch_async(dispatch_get_global_queue(0, 0), ^{
24         [self crazyTextB];
25     });
26 }
27 
28 
29 // 耗时操作A
30 -(void)crazyTextA{
31     for (int i = 10; i > 0; i--) {
32         [NSThread sleepForTimeInterval:0.5];
33         NSString *str = [NSString stringWithFormat:@"i = %d",i];
34         NSLog(@"BlockA---%@", str);
35     }
36 }
37 
38 // 耗时操作B
39 -(void)crazyTextB{
40     for (int i = 20; i > 10; i--) {
41         [NSThread sleepForTimeInterval:0.5];
42         NSString *str = [NSString stringWithFormat:@"i = %d",i];
43         NSLog(@"BlockB---%@", str);
44     }
45 }
46 
47 // 更新UI
48 -(void)updateUI{
49     NSLog(@"更新UI");
50     self.view.backgroundColor = [UIColor cyanColor];
51 }

日志信息

接下来,我们进行详细测试,进一步了解其工作原理

测试一:使用异步函数,往并发队列中添加任务

- (void)viewDidLoad {
    [super viewDidLoad];
    dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
    // 添加任务到队列中,就可以执行任务
    dispatch_async(globalQueue, ^{
        NSLog(@"异步+并发1----%@",[NSThread currentThread]); // 11
    });
    dispatch_async(globalQueue, ^{
        NSLog(@"异步+并发2----%@",[NSThread currentThread]); // 12
    });
    dispatch_async(globalQueue, ^{
        NSLog(@"异步+并发3----%@",[NSThread currentThread]); // 9
    });
    NSLog(@"主线程----%@",[NSThread mainThread]);
}

日志信息:会开辟三个新线程

测试二:使用异步函数,往串行队列中添加任务

- (void)viewDidLoad {
    [super viewDidLoad];
    // 串行队列
    dispatch_queue_t serialQueue = dispatch_queue_create("Hi GCD_A", DISPATCH_QUEUE_SERIAL);
    // 添加任务到队列中,就可以执行任务
    dispatch_async(serialQueue, ^{
        NSLog(@"异步+串行1----%@",[NSThread currentThread]);
    });
    dispatch_async(serialQueue, ^{
        NSLog(@"异步+串行2----%@",[NSThread currentThread]);
    });
    dispatch_async(serialQueue, ^{
        NSLog(@"异步+串行3----%@",[NSThread currentThread]);
    });
    NSLog(@"主线程----%@",[NSThread mainThread]);
}

日志信息:会开辟一个且只会开辟一个新线程

测试三:使用同步函数,往并发队列中添加任务

- (void)viewDidLoad {
    [super viewDidLoad];
    // 全局队列
    dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
    dispatch_sync(globalQueue, ^{
        NSLog(@"同步+并发1----%@",[NSThread currentThread]);
    });
    dispatch_sync(globalQueue, ^{
        NSLog(@"同步+并发2----%@",[NSThread currentThread]);
    });
    dispatch_sync(globalQueue, ^{
        NSLog(@"同步+并发3----%@",[NSThread currentThread]);
    });
    NSLog(@"主线程----%@",[NSThread mainThread]);
    // 依次执行: 同步+并发1、同步+并发2、同步+并发3、主线程
}

日志信息:不会开辟新线程,均在当前线程(主线程)中执行,且并发队列失去了并发功能

测试四:使用同步函数,往串行队列中添加任务 

- (void)viewDidLoad {
    [super viewDidLoad];
    // 串行队列
    dispatch_queue_t serialQueue = dispatch_queue_create("Hi GCD", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(serialQueue, ^{
        NSLog(@"同步+串行1----%@",[NSThread currentThread]); // 1
    });
    dispatch_sync(serialQueue, ^{
        NSLog(@"同步+串行2----%@",[NSThread currentThread]); // 1
    });
    dispatch_sync(serialQueue, ^{
        NSLog(@"同步+串行3----%@",[NSThread currentThread]); // 1
    });
    NSLog(@"主线程----%@",[NSThread mainThread]);
}

日志信息:不会开辟新线程,同样是在当前线程中执行 

▶ 结语

同步函数不具备开辟线程的能力,无论是什么队列都不会开辟线程

异步函数具备开辟线程的能力,并且开辟几条线程由什么样的队列决定:串行队列只会开辟一条新的线程,并发队列会开辟多条线程

主队列是和主线程相关联的队列,是 GCD 自带的一种特殊的串行队列:放在主队列中的任务都会在主线程中执行

A. 使用异步函数,往主队列中添加任务

- (void)viewDidLoad {
    [super viewDidLoad];
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"异步+主队列1--%@",[NSThread currentThread]);
    });
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"异步+主队列2--%@",[NSThread currentThread]);
    });
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"异步+主队列3--%@",[NSThread currentThread]);
    });
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"异步+主队列4--%@",[NSThread currentThread]);
    });
    NSLog(@"主线程%@", [NSThread mainThread]);
}

日志信息:不会开辟新线程

B. 使用同步函数,往主队列中添加任务,会产生死锁问题 crash

- (void)viewDidLoad{
    [super viewDidLoad];
    NSLog(@"主线程--%@", [NSThread mainThread]);
    // 主队列
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        NSLog(@"任务1--%@",[NSThread currentThread]);// crash
    });
}

C. 如图所示

 

posted on 2021-11-15 00:45  低头捡石頭  阅读(485)  评论(0编辑  收藏  举报

导航