美文网首页
2023-07-15

2023-07-15

作者: 缘來諟夢 | 来源:发表于2023-07-14 17:29 被阅读0次

    在上一篇讲解了iOS开发的三种多线程实现方式的特点和用法,在这一篇主要讲解一下GCD的用法。

    GCD(Grand Central Dispatch)又叫大中央调度,它对线程操作进行了封装,加入了很多新的特性,内部进行了效率优化,提供了简洁的C语言接口,使用更加简单高效,也是苹果公司推荐的方式。

    特点:
    • GCD 可用于多核的并行运算
    • GCD 会自动利用更多的 CPU 内核(比如双核、四核)
    • GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
    • 程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码
    • 执行任务有两种方式:同步执行(sync)和异步执行(async)。两者的主要区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。
    • 两种队列:串行队列和并发队列。两者都符合 FIFO(先进先出)的原则。两者的主要区别是:执行顺序不同,以及开启线程数不同。
    GCD队列执行区别
    image

    1、只有异步提交任务时才会开启新线程,异步提交到串行队列会开启一个新线程,异步提交到并发队列可能会开启多个线程。
    2、同步提交任务无论提交到并发队列还是串行队列,都不会开启新线程,都会直接在当前线程依次同步执行。
    3、如果当前线程是主线程,那么不可在当前线程提交同步任务,否则会造成线程死锁而报错。

    1、串行队列与并发队列dispatch_queue_t;

    创建队列

    • DISPATCH_QUEUE_SERIAL 表示串行队列,队列内任务一个接一个的执行,按照先进先出(FIFO)的顺序执行
    • DISPATCH_QUEUE_CONCURRENT 表示并发队列,队列内任务可同时并列执行,任务之间不会相互等待,执行顺序不可预测
        // 串行队列的创建方法
        dispatch_queue_t queueSerial = dispatch_queue_create("com.jzsec.GCDtest", DISPATCH_QUEUE_SERIAL);
        // 并发队列的创建方法
        dispatch_queue_t queueCon = dispatch_queue_create("com.jzsec.GCDtest", DISPATCH_QUEUE_CONCURRENT);
    
    

    获取系统队列

    • 对于串行队列,GCD 提供了的一种特殊的串行队列:主队列(Main Dispatch Queue)
    • 对于并发队列,GCD 默认提供了全局并发队列(Global Dispatch Queue)
        // 主队列的获取方法
        dispatch_queue_t queueMain = dispatch_get_main_queue();
        // 全局并发队列的获取方法
        /*优先级:
        #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
        */
        dispatch_queue_t queueGlobal = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    
    2、同步dispatch_sync与异步dispatch_async派发任务;
    • 同步指阻塞当前线程,即要等添加的耗时任务块block完成后,函数才能返回,后面的代码才可以继续执行。
    • 异步指将任务添加到队列后函数立刻返回,后面的代码不用等待添加的任务完成返回即可继续执行。异步提交无法确认任务的执行顺序。
    dispatch_sync(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]);      // 打印当前线程
            }
        });
    
    
    3、同步执行 + 并发队列

    特点:在当前线程中执行任务,不会开启新线程,执行完一个任务,再执行下一个任务。所有任务都是在当前线程(主线程)中执行的,没有开启新的线程(同步执行不具备开启新线程的能力)。

    任务按顺序执行的。按顺序执行的原因:虽然并发队列可以开启多个线程,并且同时执行多个任务。但是因为本身不能创建新线程,只有当前线程这一个线程(同步任务不具备开启新线程的能力),所以也就不存在并发。而且当前线程只有等待当前队列中正在执行的任务执行完毕之后,才能继续接着执行下面的操作(同步任务需要等待队列的任务执行结束)。所以任务只能一个接一个按顺序执行,不能同时被执行。

    - (void)syncConcurrent {
        NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
        NSLog(@"syncConcurrent---begin");
    
        dispatch_queue_t queue = dispatch_queue_create("com.jzsec.GCDtest", DISPATCH_QUEUE_CONCURRENT);
    
        dispatch_sync(queue, ^{
            // 追加任务1
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
    
        dispatch_sync(queue, ^{
            // 追加任务2
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
    
        dispatch_sync(queue, ^{
            // 追加任务3
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
    
        NSLog(@"syncConcurrent---end");
    }
    
    
    4、异步执行 + 并发队列

    特点:可以开启多个线程,任务交替(同时)执行。

    除了当前线程(主线程),系统又开启了3个线程,并且任务是交替/同时执行的。(异步执行具备开启新线程的能力。且并发队列可开启多个线程,同时执行多个任务)。
    当前线程没有等待,而是直接开启了新线程,在新线程中执行任务(异步执行不做等待,可以继续执行任务)。

    - (void)asyncConcurrent {
        NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
        NSLog(@"asyncConcurrent---begin");
    
        dispatch_queue_t queue = dispatch_queue_create("com.jzsec.GCDtest", 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_async(queue, ^{
            // 追加任务3
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
    
        NSLog(@"asyncConcurrent---end");
    }
    
    
    5、同步执行 + 串行队列

    特点:不会开启新线程,在当前线程执行任务。任务是串行的,执行完一个任务,再执行下一个任务。

    所有任务都是在当前线程(主线程)中执行的,并没有开启新的线程(同步执行不具备开启新线程的能力)。任务是按顺序执行的(串行队列每次只有一个任务被执行,任务一个接一个按顺序执行)。

    - (void)syncSerial {
        NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
        NSLog(@"syncSerial---begin");
    
        dispatch_queue_t queue = dispatch_queue_create("com.jzsec.GCDtest", DISPATCH_QUEUE_SERIAL);
    
        dispatch_sync(queue, ^{
            // 追加任务1
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        dispatch_sync(queue, ^{
            // 追加任务2
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        dispatch_sync(queue, ^{
            // 追加任务3
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
    
        NSLog(@"syncSerial---end");
    }
    
    
    6、异步执行 + 串行队列

    特点:会开启新线程,但是因为任务是串行的,执行完一个任务,再执行下一个任务。

    开启了一条新线程(异步执行具备开启新线程的能力,串行队列只开启一个线程)。任务是按顺序执行的(串行队列每次只有一个任务被执行,任务一个接一个按顺序执行)。

    - (void)asyncSerial {
        NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
        NSLog(@"asyncSerial---begin");
    
        dispatch_queue_t queue = dispatch_queue_create("com.jzsec.GCDtest", DISPATCH_QUEUE_SERIAL);
    
        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_async(queue, ^{
            // 追加任务3
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
    
        NSLog(@"asyncSerial---end");
    }
    
    
    7、同步执行 + 主队列

    特点(主线程调用):互等卡主不执行。
    特点(其他线程调用):不会开启新线程,执行完一个任务,再执行下一个任务。

    - (void)syncMain {
    
        NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
        NSLog(@"syncMain---begin");
    
        dispatch_queue_t queue = dispatch_get_main_queue();
    
        dispatch_sync(queue, ^{
            // 追加任务1
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
    
        dispatch_sync(queue, ^{
            // 追加任务2
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
    
        dispatch_sync(queue, ^{
            // 追加任务3
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
    
        NSLog(@"syncMain---end");
    }
    
    
    8、异步执行 + 主队列

    特点:只在主线程中执行任务,执行完一个任务,再执行下一个任务

    所有任务都是在当前线程(主线程)中执行的,并没有开启新的线程(虽然异步执行具备开启线程的能力,但因为是主队列,所以所有任务都在主线程中)。任务是按顺序执行的(因为主队列是串行队列,每次只有一个任务被执行,任务一个接一个按顺序执行)。

    - (void)asyncMain {
        NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
        NSLog(@"asyncMain---begin");
    
        dispatch_queue_t queue = dispatch_get_main_queue();
    
        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_async(queue, ^{
            // 追加任务3
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
    
        NSLog(@"asyncMain---end");
    }
    
    
    9、线程间通信

    在其他线程完成了耗时操作时,回到主线程

    - (void)communication {
        // 获取全局并发队列
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        // 获取主队列
        dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
        dispatch_async(queue, ^{
            // 异步追加任务
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
            }
    
            // 回到主线程
            dispatch_async(mainQueue, ^{
                // 追加在主线程中执行的任务
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
            });
        });
    }
    
    
    10、栅栏方法 dispatch_barrier_async

    在执行完栅栏前面的操作之后,才执行栅栏操作,最后再执行栅栏后边的操作。

    - (void)barrier {
        dispatch_queue_t queue = dispatch_queue_create("com.jzsec.GCDtest", 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]);      // 打印当前线程
            }
        });
    }
    
    
    11、延时执行方法 dispatch_after

    dispatch_after函数并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中。严格来说,这个时间并不是绝对准确的,但想要大致延迟执行任务,dispatch_after函数是很有效的。

    - (void)after {
        NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
        NSLog(@"asyncMain---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]);  // 打印当前线程
        });
    }
    
    
    12、一次性代码(只执行一次)dispatch_once

    能保证某段代码在程序运行过程中只被执行1次,并且即使在多线程的环境下,dispatch_once也可以保证线程安全。

    - (void)once {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            // 只执行1次的代码(这里面默认是线程安全的)
            NSLog(@"dispatch_once");
        });
    }
    
    
    13、快速迭代方法 dispatch_apply

    按照指定的次数将指定的任务追加到指定的队列中,并等待全部队列执行结束才继续执行。

    - (void)apply {
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
        NSLog(@"apply---begin");
        dispatch_apply(6, queue, ^(size_t index) {
            NSLog(@"%zd---%@",index, [NSThread currentThread]);
        });
        NSLog(@"apply---end");
    }
    
    
    14、队列组 dispatch_group_notify

    当group所有任务都执行完成之后,才执行dispatch_group_notify block 中的任务。

    - (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_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");
        });
    }
    
    
    15、队列组 dispatch_group_wait

    当所有任务执行完成之后,才执行 dispatch_group_wait 之后的操作。但是,使用dispatch_group_wait 会阻塞当前线程。

    - (void)groupWait {
        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_wait(group, DISPATCH_TIME_FOREVER);
    
        NSLog(@"group---end");
    }
    
    
    16、队列组 dispatch_group_enter、dispatch_group_leave
    • dispatch_group_enter 标志着一个任务追加到 group,执行一次,相当于 group 中未执行完毕任务数+1
    • dispatch_group_leave 标志着一个任务离开了 group,执行一次,相当于 group 中未执行完毕任务数-1。
    • 当 group 中未执行完毕任务数为0的时候,才会使dispatch_group_wait解除阻塞,以及执行追加到dispatch_group_notify中的任务。
    • 当所有任务执行完成之后,才执行 dispatch_group_notify 中的任务。
    - (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
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
            }
            dispatch_group_leave(group);
        });
    
        dispatch_group_enter(group);
        dispatch_async(queue, ^{
            // 追加任务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(), ^{
            // 等前面的异步操作都执行完毕后,回到主线程.
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
            }
            NSLog(@"group---end");
        });
        NSLog(@"method---end");
    }
    
    
    17、semaphore 线程同步
    • dispatch_semaphore_create:创建一个 Semaphore 并初始化信号的总量
    • dispatch_semaphore_signal:发送一个信号,让信号总量加 1
    • dispatch_semaphore_wait:可以使总信号量减 1,信号总量小于 0 时就会一直等待(阻塞所在线程),否则就可以正常执行。
    - (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);
    }
    
    

    参考:《iOS程序猿面试笔试宝典》
    https://www.jianshu.com/p/2d57c72016c6

    作者:祥子_HelloWorld
    链接:https://www.jianshu.com/p/cddfed22d8a8
    来源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    相关文章

      网友评论

          本文标题:2023-07-15

          本文链接:https://www.haomeiwen.com/subject/tbzvudtx.html