OC底层原理(十六)多线程与GCD应用

175 阅读48分钟

多线程iOS中有着举足轻重的地位,了解线程的工作原理,有助于提升app的质量与优化app性能.

一、进程、线程与队列

进程的定义

  • 进程是指在系统中正在运行的一个应用程序,如微信、支付宝app都是一个进程
  • 每个进程之间是独立的,每个进程均运行在其专用的且受保护的内存

线程的定义

  • 线程进程基本执行单元,一个进程的所有任务都在线程中执行
  • 进程想要执行任务,必须得有线程,进程至少要有一条线程
  • 程序启动会默认开启一条线程,这条线程被称为主线程UI线程

进程与线程的关系和区别

  • 地址空间:同一进程的线程共享本进程的地址空间,而进程之间是独立的地址空间

  • 资源拥有:同一进程内的线程共享本进程的资源内存I/Ocpu等,但是进程之间的资源是独立的

  • 一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉,所以多进程要比多线程健壮

  • 进程切换时,消耗的资源大、效率高。所以设计到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程而不能用进程

  • 执行过程:每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制

  • 线程是处理器调度的基本单位,但进程不是

可能会觉得这些理论知识很抽象,百度出来一大堆但是都不好理解,看完下面的理解就全明白了

进程与线程的关系图

可以把iOS系统想象成商场进程则是商场中的店铺线程是店铺雇佣的员工

  • 进程之间的相互独立

    • 奶茶店看不到果汁店的账目(访问不了别的进程的内存)
    • 果汁店用不了奶茶店的波霸(进程之间的资源是独立的)
  • 进程至少要有一条线程

    • 店铺至少要有一个员工(进程至少有一个线程)
    • 早上开店门的员工(相当于主线程)
  • 进程/线程崩溃的情况

    • 奶茶店倒闭了并不会牵连果汁店倒闭(进程崩溃不会对其他进程产生影响)
    • 奶茶店的收银员不干了会导致奶茶店无法正常运作(线程崩溃导致进程瘫痪)

移动开发不一定是单进程处理的,android就是多进程处理的;而iOS采用沙盒机制,这也是苹果运行能够流畅安全的一个主要原因

队列的定义

队列,又称为伫列(queue),是先进先出FIFO: First-In-First-Out)的线性表,在具体应用中通常用链表或者数组来实现。装载线程任务的队形结构。队列只允许在后端(称为rear)进行插入操作,在前端(称为front)进行删除操作。队列的操作方式和堆栈类似,唯一的区别在于队列只允许新数据在后端进行添加。

截屏2024-10-18 上午12.02.38.png

队列和线程的关系

两者是没有关系的,可以这么理解:

  • 队列负责调度任务,线程执行任务
  • 在银行(进程)中,有4个工作窗口(线程),而只有一条队伍(队列
  • 窗口(线程)只负责为排队的人办理业务,并不会管队伍(队列)是怎么排的

线程和runloop的关系

  • runloop与线程是一一对应的。一个runloop对应一个核心的线程,为什么说是核心的,是因为runloop是可以嵌套的,但是核心的只能有一个,他们的关系保存在一个全局的字典里

  • runloop是来管理线程的。当线程的runloop被开启后,线程会在执行完任务后进入休眠状态,有了任务就会被唤醒去执行任务

  • runloop在第一次获取时被创建,在线程结束时被销毁

    • 对于主线程来说,runloop在程序一启动就默认创建好了

    • 对于子线程来说,runloop懒加载的,只有当我们使用的时候才会创建,所以在子线程用定时器要注意:确保子线程的runloop被创建,不然定时器不会回调。

影响任务执行速度的因素

以下因素都会对任务的执行速度造成影响:

    1. cpu的调度
    1. 线程的执行速率
    1. 队列情况
    1. 任务执行的复杂度
    1. 任务的优先级

二、多线程

多线程原理

  • 同一时间,CPU只能处理一条线程,只有一条线程在工作(执行)。
  • 多线程并发(同时)执行,其实就是CPU执行快速地在多条线程之间调度(切换)。

多线程意义

  • 优点

    • 能适当提高程序的执行效率
    • 能适当提高资源的利用率(CPU内存
    • 线程上的任务执行完成后,线程会自动销毁
  • 缺点

    • 开启线程需要占用一定的内存空间(默认情况下,每一个线程都占512KB,创建线程大约需要90毫秒的创建时间)
    • 如果开启大量的线程,会占用大量的内存空间,降低程序的性能
    • 线程越多,CPU在调用线程上的开销就越大
    • 程序设计更加复杂,比如线程间的通信、多线程的数据共享

多线程生命周期

截屏2024-10-18 上午12.36.12.png

多线程的生命周期是:新建 - 就绪 - 运行 - 阻塞 - 死亡

  • 新建:实例化线程对象
  • 就绪:向线程对象发送start消息,线程对象被加入可调度线程池等待CPU调度。
  • 运行CPU负责调度可调度线程池中线程的执行。线程执行完成之前,状态可能会在就绪运行之间来回切换。就绪和运行之间的状态变化由CPU负责,程序员不能干预。
  • 阻塞:当满足某个预定条件时,可以使用休眠阻塞线程执行。sleepForTimeInterval(休眠指定时长),sleepUntilDate(休眠到指定日期),@synchronized(self):(互斥锁)。
  • 死亡:正常死亡,线程执行完毕。非正常死亡,当满足某个条件后,在线程内部中止执行/在主线程中止线程对象

线程池的原理

截屏2024-10-18 上午12.00.38.png

  • 线程池大小小于核心线程池大小时

    • 创建线程执行任务
  • 线程池大小大于等于核心线程池大小时

    1. 先判断线程池工作队列是否已满
    2. 若没满就将任务push进队列
    3. 若已满时,且maximumPoolSize>corePoolSize,将创建新的线程来执行任务
    4. 反之则交给饱和策略去处理
参数名代表意义
corePoolSize线程池的基本大小(核心线程池大小)
maximumPool线程池的最大大小
keepAliveTime线程池中超过corePoolSize树木的空闲线程的最大存活时间
unitkeepAliveTime参数的时间单位
workQueue任务阻塞队列
threadFactory新建线程的工厂
handler当提交的任务数超过maxmumPoolSize与workQueue之和时, 任务会交给RejectedExecutionHandler来处理

饱和策略有如下四个:

    1. AbortPolicy直接抛出RejectedExecutionExeception异常来阻止系统正常运行
    1. CallerRunsPolicy将任务回退到调用者
    1. DisOldestPolicy丢掉等待最久的任务
    1. DisCardPolicy直接丢弃任务

多线程实现方案

技术方案简介语言线程生命周期使用评率
pthread一套通用的多线程API 适用于Unix/Linux/Windows等系统 跨平台/可移植 使用难度大C程序员管理几乎不用
NSThread使用更加面向对象 简单易用,可直接操作线程对象OC程序员管理偶尔使用
GCD旨在替代NSThread等线程技术 充分利用设备的多核C自动管理经常使用
NSOperation基于GCD(底层是GCD) 比GCD多了一些更简单实用的功能 使用更加面向对象OC自动管理经常使用

GCDNSOperation的区别

  • GCD仅仅支持FIFO队列不支持异步操作之间的依赖关系设置。而NSOperation中的队列可以被重新设置优先级,从而实现不同操作的执行顺序调整。

  • NSOperation支持KVO,可以观察任务的执行状态。

  • GCD更接近底层,GCD在追求性能的底层操作来说,是速度最快的

  • 从异步操作之间的事务性,顺序性,依赖关系。GCD需要自己写更多的代码来实现,而NSOperation已经内建了这些支持

  • 如果异步操作的过程需要更多的被交互和UI呈现出来,NSOperation更好;底层代码中,任务之间不太互相依赖,而需要更高的并发能力,GCD则更有优势。

线程间通讯

  • 直接消息传递: 通过performSelector的一系列方法,可以实现由某一线程指定在另外的线程上执行任务。因为任务的执行上下文是目标线程,这种方式发送的消息将会自动的被序列化

  • 全局变量、共享内存块和对象: 在两个线程之间传递信息的另一种简单方法是使用全局变量,共享对象或共享内存块。尽管共享变量既快速又简单,但是它们比直接消息传递更脆弱。必须使用锁或其他同步机制仔细保护共享变量,以确保代码的正确性。 否则可能会导致竞争状况,数据损坏或崩溃。

  • 条件执行: 条件是一种同步工具,可用于控制线程何时执行代码的特定部分。您可以将条件视为关守,让线程仅在满足指定条件时运行。

  • Runloop sources: 一个自定义的 Runloop source 配置可以让一个线程上收到特定的应用程序消息。由于 Runloop source事件驱动的,因此在无事可做线程会自动进入睡眠状态,从而提高了线程的效率。

  • Ports and sockets: 基于端口的通信是在两个线程之间进行通信的一种更为复杂的方法,但它也是一种非常可靠的技术。更重要的是,端口和套接字可用于与外部实体(例如其他进程和服务)进行通信。为了提高效率,使用 Runloop source 来实现端口,因此当端口上没有数据等待时,线程将进入睡眠状态。

  • 消息队列: 传统的多处理服务定义了先进先出(FIFO)队列抽象,用于管理传入和传出数据。尽管消息队列既简单又方便,但是它们不如其他一些通信技术高效

  • Cocoa分布式对象: 分布式对象是一种 Cocoa 技术,可提供基于端口的通信的高级实现。尽管可以将这种技术用于线程间通信,但是强烈建议不要这样做,因为它会产生大量开销。分布式对象更适合与其他进程进行通信,尽管在这些进程之间进行事务的开销也很高

三、GCD

GCD全称是Grand Central Dispatch,它是纯 C 语言,并且提供了非常多强大的函数。

GCD的优势:

    1. GCD 是苹果公司为多核的并行运算提出的解决方案
    1. GCD 会自动利用更多的CPU内核(比如双核、四核)
    1. GCD自动管理线程的生命周期(创建线程、调度任务、销毁线程)
    1. 程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码

我们要关注的点就是:GCD的核心——将任务添加到队列,并且指定执行任务的函数

dispatch_block_t

  • 测试代码:
#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        dispatch_block_t block = ^{
            NSLog(@bloack: "%@", [NSThread currentThread]);
            NSLog(@"GCD的dispatch_block_t使用");
        };
        dispatch_queue_t queue = dispatch_queue_create("com.CJ", NULL);
        dispatch_async(queue, block);
        
        // 这一部分代码是让主线程的保活,后面留到RunLoop篇章时解析
        NSLog(@"main: %@", [NSThread currentThread]);
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode: NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] run];
    }
    return 0;
}
  • lldb打印:
2023-03-21 16:44:40.748299+0800 KCObjcBuild[31791:7882747] block: <NSThread: 0x600001710040>{number = 2, name = (null)}
2023-03-21 16:44:40.748347+0800 KCObjcBuild[31791:7882294] main: <_NSMainThread: 0x600001708180>{number = 1, name = main}
2023-03-21 16:44:41.848139+0800 KCObjcBuild[31791:7882747] GCD的dispatch_block_t使用

注意点:

由于这部分代码是在objc4-886.9源码上,这个部分是个masOCCommand Line Tool工程没有开启主线程RunLoop,具体分析留到后面。

  • 总结:

    这串代码最能体现GCD的核心:

    • dispatch_block_t使用block封装任务
    • dispatch_queue_t创建队列
    • dispatch_async将任务添加到队列
  • 上述代码通常也写成这种形式:

...
dispatch_queue_t queue = dispatch_queue_create("com.CJ", NULL);
dispatch_async(queue, ^{
    NSLog(@"block: %@", [NSThread currentThread]);
    NSLog(@"GCD的dispatch_block_t使用");
});
...

dispatch_sync & dispatch_async

多线程执行任务分为dispatch_sync同步执行任务和dispatch_async异步执行任务:

  • dispatch_sync同步执行

    • 必须等待当前语句执行完毕,才会执行下一条语句
    • 不会开启线程
    • 在当前线程执行block的任务
  • dispatch_async异步执行

    • 不用等待当前语句执行完毕,就可以执行下一条语句
    • 会开启线程执行block任务
    • 异步是多线程的代名词
  1. 只需要将之前代码的dispatch_async改成dispatch_sync就会发现没有开启新线程。
  • 测试代码:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        dispatch_block_t block = ^{
            NSLog(@bloack: "%@", [NSThread currentThread]);
            NSLog(@"GCD的dispatch_block_t使用");
        };
        dispatch_queue_t queue = dispatch_queue_create("com.CJ", NULL);
        // 将异步执行dispatch_async改为同步执行dispatch_sync
        dispatch_sync(queue, block);
        
        // 这一部分代码是让主线程的保活,后面留到RunLoop篇章时解析
        NSLog(@"main: %@", [NSThread currentThread]);
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode: NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] run];
    }
    return 0;
}

  • lldb打印:
2023-03-21 16:46:48.301994+0800 KCObjcBuild[31867:7885284] block: <_NSMainThread: 0x600001710000>{number = 1, name = main}
2023-03-21 16:46:51.966022+0800 KCObjcBuild[31867:7885284] GCD的dispatch_block_t使用
2023-03-21 16:46:51.966462+0800 KCObjcBuild[31867:7885284] main: <_NSMainThread: 0x600001710000>{number = 1, name = main}
  • 结论:

    • dispatch_sync并没有开辟新线程的能力,就是在{number = 1, name = main}主线程里;

    • dispatch_async具备这个能力,所以block内容可以在{number = 2, name = (null)}的子线程执行。

dispatch_queue_t

截屏2024-10-18 上午1.03.20.png

多线程中队列分为串行队列(Serial Dispatch Queue)和并发队列(Concurrent Dispatch Queue):

  • 串行队列:线程执行只能依次逐一先后有序的执行,等待上一个执行完再执行下一个

    • 使用dispatch_queue_create("xxx", DISPATCH_QUEUE_SERIAL)创建串行队列
    • 亦可以使用dispatch_queue_create("xxx", NULL)创建串行队列(GCD底层会讲到)
  • 主队列:绑定主线程,所有任务都在主线程中执行、经过特殊处理的串行的队列

    • 使用dispatch_get_main_queue()获取主队列
  • 并发队列:线程可以同时一起执行,不需要等待上一个执行完就能执行下一个任务

    • 使用dispatch_queue_create("xxx", DISPATCH_QUEUE_CONCURRENT);创建并发队列
  • 全局队列:系统提供的并发队列

    • 最简单的是使用dispatch_get_global_queue(0, 0)获取系统提供的并发队列
      • 第一个参数是优先级枚举值,默认优先级为DISPATCH_QUEUE_PRIORITY_DEFAULT=0
      • 优先级从高到低依次为DISPATCH_QUEUE_PRIORITY_HIGHDISPATCH_QUEUE_PRIORITY_DEFAULTDISPATCH_QUEUE_PRIORITY_LOWDISPATCH_QUEUE_PRIORITY_BACKGROUND

串行/并发和同步/异步的排列组合

截屏2024-10-18 上午1.16.21.png

主队列和全局队列单独考虑,组合结果以总结表格为准

串行+同步

任务一个接一个执行,不开辟线程

  • 测试代码:
#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        dispatch_queue_t queue = dispatch_queue_create("com.CJ", DISPATCH_QUEUE_SERIAL);
        for (int i = 0; i < 10; i++) {
            dispatch_sync(queue, ^{
                NSLog(@"串行&同步线程%d-%@", i, [NSThread currentThread]);
            });
        }
        
        
        NSLog(@"main主线程: %@", [NSThread currentThread]);
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode: NSRunLoopCommonModes];
        [[NSRunLoop currentRunLoop] run];
    }
    return 0;
}
  • lldb打印:
2023-03-21 17:17:04.454401+0800 KCObjcBuild[32879:7919356] 串行&同步线程0-<_NSMainThread: 0x600001708200>{number = 1, name = main}
2023-03-21 17:17:05.788468+0800 KCObjcBuild[32879:7919356] 串行&同步线程1-<_NSMainThread: 0x600001708200>{number = 1, name = main}
2023-03-21 17:17:06.648287+0800 KCObjcBuild[32879:7919356] 串行&同步线程2-<_NSMainThread: 0x600001708200>{number = 1, name = main}
2023-03-21 17:17:09.508551+0800 KCObjcBuild[32879:7919356] 串行&同步线程3-<_NSMainThread: 0x600001708200>{number = 1, name = main}
2023-03-21 17:17:10.299730+0800 KCObjcBuild[32879:7919356] 串行&同步线程4-<_NSMainThread: 0x600001708200>{number = 1, name = main}
2023-03-21 17:17:10.986423+0800 KCObjcBuild[32879:7919356] 串行&同步线程5-<_NSMainThread: 0x600001708200>{number = 1, name = main}
2023-03-21 17:17:11.225806+0800 KCObjcBuild[32879:7919356] 串行&同步线程6-<_NSMainThread: 0x600001708200>{number = 1, name = main}
2023-03-21 17:17:11.814978+0800 KCObjcBuild[32879:7919356] 串行&同步线程7-<_NSMainThread: 0x600001708200>{number = 1, name = main}
2023-03-21 17:17:12.069106+0800 KCObjcBuild[32879:7919356] 串行&同步线程8-<_NSMainThread: 0x600001708200>{number = 1, name = main}
2023-03-21 17:17:12.652182+0800 KCObjcBuild[32879:7919356] 串行&同步线程9-<_NSMainThread: 0x600001708200>{number = 1, name = main}
2023-03-21 17:17:12.909856+0800 KCObjcBuild[32879:7919356] main主线程: <_NSMainThread: 0x600001708200>{number = 1, name = main}

串行+异步

任务一个接一个执行,会开辟线程

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        dispatch_queue_t queue = dispatch_queue_create("com.CJ", DISPATCH_QUEUE_SERIAL);
        for (int i = 0; i < 10; i++) {
            dispatch_async(queue, ^{
                NSLog(@"串行&异步线程%d-%@", i, [NSThread currentThread]);
            });
        }
        
        
        NSLog(@"main主线程: %@", [NSThread currentThread]);
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode: NSRunLoopCommonModes];
        [[NSRunLoop currentRunLoop] run];
    }
    return 0;
}
  • lldb打印:
2023-03-21 17:21:58.570513+0800 KCObjcBuild[33031:7924697] 串行&异步线程0-<NSThread: 0x6000017043c0>{number = 2, name = (null)}
2023-03-21 17:21:58.570522+0800 KCObjcBuild[33031:7924604] main主线程: <_NSMainThread: 0x600001710200>{number = 1, name = main}
2023-03-21 17:21:59.580030+0800 KCObjcBuild[33031:7924697] 串行&异步线程1-<NSThread: 0x6000017043c0>{number = 2, name = (null)}
2023-03-21 17:22:00.301317+0800 KCObjcBuild[33031:7924697] 串行&异步线程2-<NSThread: 0x6000017043c0>{number = 2, name = (null)}
2023-03-21 17:22:00.946453+0800 KCObjcBuild[33031:7924697] 串行&异步线程3-<NSThread: 0x6000017043c0>{number = 2, name = (null)}
2023-03-21 17:22:04.696778+0800 KCObjcBuild[33031:7924697] 串行&异步线程4-<NSThread: 0x6000017043c0>{number = 2, name = (null)}
2023-03-21 17:22:04.697078+0800 KCObjcBuild[33031:7924697] 串行&异步线程5-<NSThread: 0x6000017043c0>{number = 2, name = (null)}
2023-03-21 17:22:04.697302+0800 KCObjcBuild[33031:7924697] 串行&异步线程6-<NSThread: 0x6000017043c0>{number = 2, name = (null)}
2023-03-21 17:22:04.697907+0800 KCObjcBuild[33031:7924697] 串行&异步线程7-<NSThread: 0x6000017043c0>{number = 2, name = (null)}
2023-03-21 17:22:04.698177+0800 KCObjcBuild[33031:7924697] 串行&异步线程8-<NSThread: 0x6000017043c0>{number = 2, name = (null)}
2023-03-21 17:22:04.698593+0800 KCObjcBuild[33031:7924697] 串行&异步线程9-<NSThread: 0x6000017043c0>{number = 2, name = (null)}

并发+同步

任务一个接一个执行,不开辟线程

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        dispatch_queue_t queue = dispatch_queue_create("com.CJ", DISPATCH_QUEUE_CONCURRENT);
        for (int i = 0; i < 10; i++) {
            dispatch_sync(queue, ^{
                NSLog(@"并发&同步线程%d-%@", i, [NSThread currentThread]);
            });
        }

        
        
        NSLog(@"main主线程: %@", [NSThread currentThread]);
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode: NSRunLoopCommonModes];
        [[NSRunLoop currentRunLoop] run];
    }
    return 0;
}
  • lldb打印:
2023-03-21 17:25:02.703746+0800 KCObjcBuild[33139:7928295] 并发&同步线程0-<_NSMainThread: 0x60000170c180>{number = 1, name = main}
2023-03-21 17:25:02.704235+0800 KCObjcBuild[33139:7928295] 并发&同步线程1-<_NSMainThread: 0x60000170c180>{number = 1, name = main}
2023-03-21 17:25:02.704393+0800 KCObjcBuild[33139:7928295] 并发&同步线程2-<_NSMainThread: 0x60000170c180>{number = 1, name = main}
2023-03-21 17:25:02.704561+0800 KCObjcBuild[33139:7928295] 并发&同步线程3-<_NSMainThread: 0x60000170c180>{number = 1, name = main}
2023-03-21 17:25:02.704856+0800 KCObjcBuild[33139:7928295] 并发&同步线程4-<_NSMainThread: 0x60000170c180>{number = 1, name = main}
2023-03-21 17:25:02.705101+0800 KCObjcBuild[33139:7928295] 并发&同步线程5-<_NSMainThread: 0x60000170c180>{number = 1, name = main}
2023-03-21 17:25:02.705359+0800 KCObjcBuild[33139:7928295] 并发&同步线程6-<_NSMainThread: 0x60000170c180>{number = 1, name = main}
2023-03-21 17:25:02.705513+0800 KCObjcBuild[33139:7928295] 并发&同步线程7-<_NSMainThread: 0x60000170c180>{number = 1, name = main}
2023-03-21 17:25:02.705741+0800 KCObjcBuild[33139:7928295] 并发&同步线程8-<_NSMainThread: 0x60000170c180>{number = 1, name = main}
2023-03-21 17:25:02.707847+0800 KCObjcBuild[33139:7928295] 并发&同步线程9-<_NSMainThread: 0x60000170c180>{number = 1, name = main}
2023-03-21 17:25:04.336092+0800 KCObjcBuild[33139:7928295] main主线程: <_NSMainThread: 0x60000170c180>{number = 1, name = main}

并发+异步

任务乱序执行,开辟线程

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        dispatch_queue_t queue = dispatch_queue_create("com.CJ", DISPATCH_QUEUE_CONCURRENT);
        for (int i = 0; i < 10; i++) {
            dispatch_async(queue, ^{
                NSLog(@"并发&异步线程%d-%@", i, [NSThread currentThread]);
            });
        }

        NSLog(@"main主线程: %@", [NSThread currentThread]);
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode: NSRunLoopCommonModes];
        [[NSRunLoop currentRunLoop] run];
    }
    return 0;
}
  • lldb打印:
2023-03-21 17:27:15.884189+0800 KCObjcBuild[33219:7931066] main主线程: <_NSMainThread: 0x600001708000>{number = 1, name = main}
2023-03-21 17:27:15.885670+0800 KCObjcBuild[33219:7931117] 并发&异步线程0-<NSThread: 0x600001714240>{number = 11, name = (null)}
2023-03-21 17:27:15.885698+0800 KCObjcBuild[33219:7931125] 并发&异步线程1-<NSThread: 0x6000017005c0>{number = 2, name = (null)}
2023-03-21 17:27:15.885716+0800 KCObjcBuild[33219:7931126] 并发&异步线程2-<NSThread: 0x6000017182c0>{number = 4, name = (null)}
2023-03-21 17:27:15.885742+0800 KCObjcBuild[33219:7931131] 并发&异步线程7-<NSThread: 0x600001710140>{number = 10, name = (null)}
2023-03-21 17:27:15.885754+0800 KCObjcBuild[33219:7931128] 并发&异步线程4-<NSThread: 0x6000017141c0>{number = 3, name = (null)}
2023-03-21 17:27:15.885788+0800 KCObjcBuild[33219:7931132] 并发&异步线程8-<NSThread: 0x600001700500>{number = 9, name = (null)}
2023-03-21 17:27:15.885794+0800 KCObjcBuild[33219:7931129] 并发&异步线程5-<NSThread: 0x6000017100c0>{number = 6, name = (null)}
2023-03-21 17:27:15.885979+0800 KCObjcBuild[33219:7931127] 并发&异步线程3-<NSThread: 0x600001710100>{number = 5, name = (null)}
2023-03-21 17:27:15.886079+0800 KCObjcBuild[33219:7931130] 并发&异步线程6-<NSThread: 0x60000170c000>{number = 7, name = (null)}
2023-03-21 17:27:15.886184+0800 KCObjcBuild[33219:7931133] 并发&异步线程9-<NSThread: 0x600001718080>{number = 8, name = (null)}

下面来看一下主队列全局队列的使用情况:

主队列+同步

相互等待,造成死锁

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        dispatch_queue_t queue = dispatch_get_main_queue();
        for (int i = 0; i < 10; i++) {
            dispatch_sync(queue, ^{
                NSLog(@"主队列&同步线程%d-%@", i, [NSThread currentThread]);
            });
        }

        
        
        NSLog(@"main主线程: %@", [NSThread currentThread]);
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode: NSRunLoopCommonModes];
        [[NSRunLoop currentRunLoop] run];
    }
    return 0;
}

主队列+异步

任务一个接一个执行,不开辟线程

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        dispatch_queue_t queue = dispatch_get_main_queue();
        for (int i = 0; i < 10; i++) {
            dispatch_async(queue, ^{
                NSLog(@"主队列&异步线程%d-%@", i, [NSThread currentThread]);
            });
        }

        
        
        NSLog(@"main主线程: %@", [NSThread currentThread]);
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode: NSRunLoopCommonModes];
        [[NSRunLoop currentRunLoop] run];
    }
    return 0;
}
  • lldb打印:
2023-03-22 02:02:40.586122+0800 KCObjcBuild[47723:8317933] main主线程: <_NSMainThread: 0x600001700200>{number = 1, name = main}
2023-03-22 02:02:43.044761+0800 KCObjcBuild[47723:8317933] 主队列&异步线程0-<_NSMainThread: 0x600001700200>{number = 1, name = main}
2023-03-22 02:02:43.044982+0800 KCObjcBuild[47723:8317933] 主队列&异步线程1-<_NSMainThread: 0x600001700200>{number = 1, name = main}
2023-03-22 02:02:43.045108+0800 KCObjcBuild[47723:8317933] 主队列&异步线程2-<_NSMainThread: 0x600001700200>{number = 1, name = main}
2023-03-22 02:02:43.045221+0800 KCObjcBuild[47723:8317933] 主队列&异步线程3-<_NSMainThread: 0x600001700200>{number = 1, name = main}
2023-03-22 02:02:43.045357+0800 KCObjcBuild[47723:8317933] 主队列&异步线程4-<_NSMainThread: 0x600001700200>{number = 1, name = main}
2023-03-22 02:02:43.045561+0800 KCObjcBuild[47723:8317933] 主队列&异步线程5-<_NSMainThread: 0x600001700200>{number = 1, name = main}
2023-03-22 02:02:43.045932+0800 KCObjcBuild[47723:8317933] 主队列&异步线程6-<_NSMainThread: 0x600001700200>{number = 1, name = main}
2023-03-22 02:02:43.046068+0800 KCObjcBuild[47723:8317933] 主队列&异步线程7-<_NSMainThread: 0x600001700200>{number = 1, name = main}
2023-03-22 02:02:43.046180+0800 KCObjcBuild[47723:8317933] 主队列&异步线程8-<_NSMainThread: 0x600001700200>{number = 1, name = main}
2023-03-22 02:02:43.046290+0800 KCObjcBuild[47723:8317933] 主队列&异步线程9-<_NSMainThread: 0x600001700200>{number = 1, name = main}

全局队列+同步

任务一个接一个执行,不开辟线程(同并发+同步)

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        for (int i = 0; i < 10; i++) {
            dispatch_sync(queue, ^{
                NSLog(@"全局队列&同步线程%d-%@", i, [NSThread currentThread]);
            });
        }
 
        NSLog(@"main主线程: %@", [NSThread currentThread]);
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode: NSRunLoopCommonModes];
        [[NSRunLoop currentRunLoop] run];
    }
    return 0;
}
  • lldb打印:
2023-03-22 02:08:03.087775+0800 KCObjcBuild[47909:8324568] 全局队列&同步线程0-<_NSMainThread: 0x600001710180>{number = 1, name = main}
2023-03-22 02:08:03.087971+0800 KCObjcBuild[47909:8324568] 全局队列&同步线程1-<_NSMainThread: 0x600001710180>{number = 1, name = main}
2023-03-22 02:08:03.088163+0800 KCObjcBuild[47909:8324568] 全局队列&同步线程2-<_NSMainThread: 0x600001710180>{number = 1, name = main}
2023-03-22 02:08:03.088236+0800 KCObjcBuild[47909:8324568] 全局队列&同步线程3-<_NSMainThread: 0x600001710180>{number = 1, name = main}
2023-03-22 02:08:03.088297+0800 KCObjcBuild[47909:8324568] 全局队列&同步线程4-<_NSMainThread: 0x600001710180>{number = 1, name = main}
2023-03-22 02:08:03.088355+0800 KCObjcBuild[47909:8324568] 全局队列&同步线程5-<_NSMainThread: 0x600001710180>{number = 1, name = main}
2023-03-22 02:08:03.088413+0800 KCObjcBuild[47909:8324568] 全局队列&同步线程6-<_NSMainThread: 0x600001710180>{number = 1, name = main}
2023-03-22 02:08:03.088470+0800 KCObjcBuild[47909:8324568] 全局队列&同步线程7-<_NSMainThread: 0x600001710180>{number = 1, name = main}
2023-03-22 02:08:03.088527+0800 KCObjcBuild[47909:8324568] 全局队列&同步线程8-<_NSMainThread: 0x600001710180>{number = 1, name = main}
2023-03-22 02:08:03.088583+0800 KCObjcBuild[47909:8324568] 全局队列&同步线程9-<_NSMainThread: 0x600001710180>{number = 1, name = main}
2023-03-22 02:08:07.797821+0800 KCObjcBuild[47909:8324568] main主线程: <_NSMainThread: 0x600001710180>{number = 1, name = main}

全局队列+异步

任务乱序执行,开辟线程(同并发+异步)

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        for (int i = 0; i < 10; i++) {
            dispatch_async(queue, ^{
                NSLog(@"全局队列&异步线程%d-%@", i, [NSThread currentThread]);
            });
        }

        NSLog(@"main主线程: %@", [NSThread currentThread]);
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode: NSRunLoopCommonModes];
        [[NSRunLoop currentRunLoop] run];
    }
    return 0;
}
  • lldb打印:
2023-03-22 02:08:47.402335+0800 KCObjcBuild[47942:8325947] 全局队列&异步线程0-<NSThread: 0x600001704340>{number = 3, name = (null)}
2023-03-22 02:08:47.402336+0800 KCObjcBuild[47942:8325962] 全局队列&异步线程4-<NSThread: 0x6000017083c0>{number = 6, name = (null)}
2023-03-22 02:08:47.402340+0800 KCObjcBuild[47942:8325959] 全局队列&异步线程1-<NSThread: 0x600001710180>{number = 2, name = (null)}
2023-03-22 02:08:47.402360+0800 KCObjcBuild[47942:8325965] 全局队列&异步线程7-<NSThread: 0x600001714140>{number = 9, name = (null)}
2023-03-22 02:08:47.402343+0800 KCObjcBuild[47942:8325960] 全局队列&异步线程2-<NSThread: 0x60000170ca80>{number = 4, name = (null)}
2023-03-22 02:08:47.402337+0800 KCObjcBuild[47942:8325963] 全局队列&异步线程5-<NSThread: 0x6000017180c0>{number = 7, name = (null)}
2023-03-22 02:08:47.402348+0800 KCObjcBuild[47942:8325961] 全局队列&异步线程3-<NSThread: 0x600001700000>{number = 5, name = (null)}
2023-03-22 02:08:47.402408+0800 KCObjcBuild[47942:8325964] 全局队列&异步线程6-<NSThread: 0x60000171c080>{number = 8, name = (null)}
2023-03-22 02:08:47.402540+0800 KCObjcBuild[47942:8325879] main主线程: <_NSMainThread: 0x60000170c0c0>{number = 1, name = main}
2023-03-22 02:08:47.402922+0800 KCObjcBuild[47942:8325962] 全局队列&异步线程8-<NSThread: 0x6000017083c0>{number = 6, name = (null)}
2023-03-22 02:08:47.402940+0800 KCObjcBuild[47942:8325947] 全局队列&异步线程9-<NSThread: 0x600001704340>{number = 3, name = (null)}

总结一下:

执行\队列串行队列并发队列主队列全局队列
同步执行按序执行,不开辟线程按序执行,不开辟线程死锁按序执行,不开辟线程
异步执行按序执行,开辟线程乱序执行,开辟线程按序执行,不开辟线程乱序执行,开辟线程

dispatch_after

dispatch_after表示在某队列中的block延迟执行

应用场景:在主队列上延迟执行一项任务,如viewDidload之后延迟1s,提示一个alertview(是延迟加入到队列,而不是延迟执行)

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    NSLog(@"2秒后输出");
});

dispatch_once

dispatch_once保证在App运行期间,block中的代码只执行一次

应用场景:单例method-Swizzling

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    //创建单例、method swizzled或其他任务
});

dispatch_apply

dispatch_apply将指定的Block追加到指定的队列中重复执行,并等到全部的处理执行结束——相当于线程安全的for循环

应用场景:用来拉取网络数据后提前算出各个控件的大小,防止绘制时计算,提高表单滑动流畅性

  • 添加到串行队列中——按序执行
  • 添加到主队列中——死锁
  • 添加到并发队列中——乱序执行
  • 添加到全局队列中——乱序执行
#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
     /**
     param1:重复次数
     param2:追加的队列
     param3:执行任务
     **/
        dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_SERIAL);
        NSLog(@"dispatch_apply前");
        dispatch_apply(10, queue, ^(size_t index) {
            NSLog(@"dispatch_apply的线程%zu-%@", index, [NSThread currentThread]);
        });
        NSLog(@"dispatch_apply后");

        NSLog(@"main主线程: %@", [NSThread currentThread]);
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode: NSRunLoopCommonModes];
        [[NSRunLoop currentRunLoop] run];
    }
    return 0;
}
  • lldb打印:
2023-03-23 13:16:49.402081+0800 KCObjcBuild[88147:9359978] dispatch_apply的线程0-<_NSMainThread: 0x600001704040>{number = 1, name = main}
2023-03-23 13:16:49.402302+0800 KCObjcBuild[88147:9359978] dispatch_apply的线程1-<_NSMainThread: 0x600001704040>{number = 1, name = main}
2023-03-23 13:16:49.402482+0800 KCObjcBuild[88147:9359978] dispatch_apply的线程2-<_NSMainThread: 0x600001704040>{number = 1, name = main}
2023-03-23 13:16:49.402571+0800 KCObjcBuild[88147:9359978] dispatch_apply的线程3-<_NSMainThread: 0x600001704040>{number = 1, name = main}
2023-03-23 13:16:49.402647+0800 KCObjcBuild[88147:9359978] dispatch_apply的线程4-<_NSMainThread: 0x600001704040>{number = 1, name = main}
2023-03-23 13:16:49.402718+0800 KCObjcBuild[88147:9359978] dispatch_apply的线程5-<_NSMainThread: 0x600001704040>{number = 1, name = main}
2023-03-23 13:16:49.402786+0800 KCObjcBuild[88147:9359978] dispatch_apply的线程6-<_NSMainThread: 0x600001704040>{number = 1, name = main}
2023-03-23 13:16:49.402854+0800 KCObjcBuild[88147:9359978] dispatch_apply的线程7-<_NSMainThread: 0x600001704040>{number = 1, name = main}
2023-03-23 13:16:49.402935+0800 KCObjcBuild[88147:9359978] dispatch_apply的线程8-<_NSMainThread: 0x600001704040>{number = 1, name = main}
2023-03-23 13:16:49.403095+0800 KCObjcBuild[88147:9359978] dispatch_apply的线程9-<_NSMainThread: 0x600001704040>{number = 1, name = main}
2023-03-23 13:16:49.403201+0800 KCObjcBuild[88147:9359978] dispatch_apply后
2023-03-23 13:16:49.403293+0800 KCObjcBuild[88147:9359978] main主线程: <_NSMainThread: 0x600001704040>{number = 1, name = main}

dispatch_group_t

dispatch_group_t:调度组将任务分组执行,能监听任务组完成,并设置等待时间

应用场景:多个接口请求之后刷新页面

dispatch_group_async

dispatch_group_notifydispatch_group_async执行结束之后会受到通知


#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        dispatch_group_t group = dispatch_group_create();
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        dispatch_group_async(group, queue, ^{
            NSLog(@"请求一线程: %@", [NSThread currentThread]);
            NSLog(@"请求一完成");
        });

        dispatch_group_async(group, queue, ^{
            NSLog(@"请求二线程: %@", [NSThread currentThread]);
            NSLog(@"请求二完成");
        });

        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            NSLog(@"刷新线程: %@", [NSThread currentThread]);
            NSLog(@"刷新页面");
        });

      
        //NSLog(@"main主线程: %@", [NSThread currentThread]);
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode: NSRunLoopCommonModes];
        [[NSRunLoop currentRunLoop] run];
    }
    return 0;
}
  • lldb打印:
2023-03-23 13:37:06.325916+0800 KCObjcBuild[88637:9378505] 请求二线程: <NSThread: 0x6000017147c0>{number = 2, name = (null)}
2023-03-23 13:37:06.325916+0800 KCObjcBuild[88637:9378504] 请求一线程: <NSThread: 0x600001704440>{number = 3, name = (null)}
2023-03-23 13:37:06.326247+0800 KCObjcBuild[88637:9378504] 请求一完成
2023-03-23 13:37:06.326249+0800 KCObjcBuild[88637:9378505] 请求二完成
2023-03-23 13:37:08.352379+0800 KCObjcBuild[88637:9378458] 刷新线程: <_NSMainThread: 0x600001714100>{number = 1, name = main}
2023-03-23 13:37:09.559744+0800 KCObjcBuild[88637:9378458] 刷新页面

dispatch_group_enter & dispatch_group_leave

dispatch_group_enterdispatch_group_leave成对出现,使进出组的逻辑更加清晰

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        dispatch_group_t group = dispatch_group_create();
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        dispatch_group_enter(group);
        dispatch_async(queue, ^{
            NSLog(@"请求一线程: %@", [NSThread currentThread]);
            NSLog(@"请求一完成");
            dispatch_group_leave(group);
        });
        
        dispatch_group_enter(group);
        dispatch_async(queue, ^{
            NSLog(@"请求二线程: %@", [NSThread currentThread]);
            NSLog(@"请求二完成");
            dispatch_group_leave(group);
        });
        
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            NSLog(@"刷新线程: %@", [NSThread currentThread]);
            NSLog(@"刷新页面");
        });

      
        //NSLog(@"main主线程: %@", [NSThread currentThread]);
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode: NSRunLoopCommonModes];
        [[NSRunLoop currentRunLoop] run];
    }
    return 0;
}

调度组要注意搭配使用,必须先进组再出组,缺一不可

dispatch_group_wait使用

long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout)

  • group:需要等待的调度组

  • timeout:等待的超时时间(即等多久)

    • 设置为DISPATCH_TIME_NOW意味着不等待直接判定调度组是否执行完毕
    • 设置为DISPATCH_TIME_FOREVER则会阻塞当前调度组,直到调度组执行完毕
  • 返回值:为long类型

    • 返回值为0——在指定时间内调度组完成了任务
    • 返回值不为0——在指定时间内调度组没有按时完成任务

将上述调度组代码进行改写


#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        dispatch_group_t group = dispatch_group_create();
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        dispatch_group_enter(group);
        dispatch_async(queue, ^{
            NSLog(@"请求一线程: %@", [NSThread currentThread]);
            NSLog(@"请求一完成");
            dispatch_group_leave(group);
        });
        
        dispatch_group_enter(group);
        dispatch_async(queue, ^{
            NSLog(@"请求二线程: %@", [NSThread currentThread]);
            NSLog(@"请求二完成");
            dispatch_group_leave(group);
        });
        
        long timeout = dispatch_group_wait(group, DISPATCH_TIME_NOW);
        //    long timeout = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
        //    long timeout = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC));
        NSLog(@"timeout=%ld", timeout);
        if (timeout == 0) {
            NSLog(@"按时完成任务");
        } else {
            NSLog(@"超时");
        }
        
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            NSLog(@"刷新线程: %@", [NSThread currentThread]);
            NSLog(@"刷新页面");
        });

      
        //NSLog(@"main主线程: %@", [NSThread currentThread]);
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode: NSRunLoopCommonModes];
        [[NSRunLoop currentRunLoop] run];
    }
    return 0;
}
  • lldb打印:
2023-03-23 13:56:29.806031+0800 KCObjcBuild[89040:9394490] timeout=49
2023-03-23 13:56:29.806209+0800 KCObjcBuild[89040:9394490] 超时
2023-03-23 13:56:29.806382+0800 KCObjcBuild[89040:9394552] 请求一线程: <NSThread: 0x600001700700>{number = 2, name = (null)}
2023-03-23 13:56:29.806389+0800 KCObjcBuild[89040:9394553] 请求二线程: <NSThread: 0x600001708300>{number = 3, name = (null)}
2023-03-23 13:56:29.806514+0800 KCObjcBuild[89040:9394552] 请求一完成
2023-03-23 13:56:29.806518+0800 KCObjcBuild[89040:9394553] 请求二完成
2023-03-23 13:56:31.928164+0800 KCObjcBuild[89040:9394490] 刷新线程: <_NSMainThread: 0x6000017002c0>{number = 1, name = main}
2023-03-23 13:56:33.719290+0800 KCObjcBuild[89040:9394490] 刷新页面

dispatch_barrier_sync & dispatch_barrier_async

应用场景:同步锁

截屏2024-10-18 上午1.29.39.png

前文已经提过并发执行异步队列会开辟线程,而任务也会因为任务复杂度和cpu的调度导致各个乱序执行完毕,比如上图中的任务3明明是先于任务4执行,但是晚于任务4执行完毕

此时GCD就提供了两个API——dispatch_barrier_syncdispatch_barrier_async,使用这两个API就能将多个任务进行分组——等栅栏前追加到队列中的任务执行完毕后,再将栅栏后的任务追加到队列中。简而言之,就是先执行栅栏前任务,再执行栅栏任务,最后执行栅栏后任务

串行队列使用栅栏函数

#import <Foundation/Foundation.h> 

int main(int argc, const char * argv[]) { 
    @autoreleasepool {
        dispatch_queue_t queue = dispatch_queue_create("CJ", DISPATCH_QUEUE_SERIAL);
        NSLog(@"开始--%@", [NSThread currentThread]);
        dispatch_async(queue, ^{
            sleep(2);
            NSLog(@"延时2s的任务1--%@", [NSThread currentThread]);
        });
        NSLog(@"第一次结束--%@", [NSThread currentThread]);
        
//        dispatch_barrier_async(queue, ^{
//            NSLog(@"---栅栏任务---");
//        });
//        NSLog(@"栅栏结束--%@", [NSThread currentThread]);
        
        dispatch_async(queue, ^{
            sleep(1);
            NSLog(@"延时1s的任务2--%@", [NSThread currentThread]);
        });
        NSLog(@"第二次结束--%@", [NSThread currentThread]);
        
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode: NSRunLoopCommonModes];
        [[NSRunLoop currentRunLoop] run];
    }
    return 0;
}
  • 不使用栅栏函数:
2023-03-23 14:31:05.469536+0800 KCObjcBuild[89532:9412635] 开始--<_NSMainThread: 0x60000170c200>{number = 1, name = main}
2023-03-23 14:31:05.470300+0800 KCObjcBuild[89532:9412635] 第一次结束--<_NSMainThread: 0x60000170c200>{number = 1, name = main}
2023-03-23 14:31:05.470542+0800 KCObjcBuild[89532:9412635] 第二次结束--<_NSMainThread: 0x60000170c200>{number = 1, name = main}
2023-03-23 14:31:07.471832+0800 KCObjcBuild[89532:9412671] 延时2s的任务1--<NSThread: 0x6000017145c0>{number = 2, name = (null)}
2023-03-23 14:31:08.475435+0800 KCObjcBuild[89532:9412671] 延时1s的任务2--<NSThread: 0x6000017145c0>{number = 2, name = (null)}

  • 使用栅栏函数
2023-03-23 14:34:37.603659+0800 KCObjcBuild[89612:9415329] 开始--<_NSMainThread: 0x600001704140>{number = 1, name = main}
2023-03-23 14:34:37.604184+0800 KCObjcBuild[89612:9415329] 第一次结束--<_NSMainThread: 0x600001704140>{number = 1, name = main}
2023-03-23 14:34:37.604338+0800 KCObjcBuild[89612:9415329] 栅栏结束--<_NSMainThread: 0x600001704140>{number = 1, name = main}
2023-03-23 14:34:37.604444+0800 KCObjcBuild[89612:9415329] 第二次结束--<_NSMainThread: 0x600001704140>{number = 1, name = main}
2023-03-23 14:34:39.609508+0800 KCObjcBuild[89612:9415377] 延时2s的任务1--<NSThread: 0x600001704900>{number = 2, name = (null)}
2023-03-23 14:34:39.609827+0800 KCObjcBuild[89612:9415377] ---栅栏任务---
2023-03-23 14:34:40.613149+0800 KCObjcBuild[89612:9415377] 延时1s的任务2--<NSThread: 0x600001704900>{number = 2, name = (null)}
  • 结论:

栅栏函数的作用是将队列中的任务进行分组,所以我们只要关注任务1任务2

截屏2024-10-18 上午1.35.58.png

结论:由于串行队列异步执行任务是一个接一个执行完毕的,所以使用栅栏函数没意义

并发队列使用栅栏函数

#import <Foundation/Foundation.h> 

int main(int argc, const char * argv[]) { 
    @autoreleasepool {
        dispatch_queue_t queue = dispatch_queue_create("CJ", DISPATCH_QUEUE_CONCURRENT);
        NSLog(@"开始--%@", [NSThread currentThread]);
        dispatch_async(queue, ^{
            sleep(2);
            NSLog(@"延时2s的任务1--%@", [NSThread currentThread]);
        });
        NSLog(@"第一次结束--%@", [NSThread currentThread]);
        
//        dispatch_barrier_async(queue, ^{
//            NSLog(@"---栅栏任务---");
//        });
//        NSLog(@"栅栏结束--%@", [NSThread currentThread]);
        
        dispatch_async(queue, ^{
            sleep(1);
            NSLog(@"延时1s的任务2--%@", [NSThread currentThread]);
        });
        NSLog(@"第二次结束--%@", [NSThread currentThread]);
        
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode: NSRunLoopCommonModes];
        [[NSRunLoop currentRunLoop] run];
    }
    return 0;
}
  • 不使用栅栏函数:
2023-03-23 14:36:58.526690+0800 KCObjcBuild[89697:9418226] 开始--<_NSMainThread: 0x600001700200>{number = 1, name = main}
2023-03-23 14:36:58.527345+0800 KCObjcBuild[89697:9418226] 第一次结束--<_NSMainThread: 0x600001700200>{number = 1, name = main}
2023-03-23 14:36:58.527570+0800 KCObjcBuild[89697:9418226] 第二次结束--<_NSMainThread: 0x600001700200>{number = 1, name = main}
2023-03-23 14:36:59.699890+0800 KCObjcBuild[89697:9418272] 延时1s的任务2--<NSThread: 0x600001700dc0>{number = 2, name = (null)}
2023-03-23 14:37:00.528495+0800 KCObjcBuild[89697:9418271] 延时2s的任务1--<NSThread: 0x600001700fc0>{number = 3, name = (null)}

使用栅栏函数

2023-03-23 14:39:06.706568+0800 KCObjcBuild[89749:9420049] 开始--<_NSMainThread: 0x6000017002c0>{number = 1, name = main}
2023-03-23 14:39:06.707151+0800 KCObjcBuild[89749:9420049] 第一次结束--<_NSMainThread: 0x6000017002c0>{number = 1, name = main}
2023-03-23 14:39:06.707377+0800 KCObjcBuild[89749:9420049] 栅栏结束--<_NSMainThread: 0x6000017002c0>{number = 1, name = main}
2023-03-23 14:39:06.707566+0800 KCObjcBuild[89749:9420049] 第二次结束--<_NSMainThread: 0x6000017002c0>{number = 1, name = main}
2023-03-23 14:39:08.708010+0800 KCObjcBuild[89749:9420092] 延时2s的任务1--<NSThread: 0x600001714300>{number = 2, name = (null)}
2023-03-23 14:39:08.708383+0800 KCObjcBuild[89749:9420092] ---栅栏任务---
2023-03-23 14:39:09.713896+0800 KCObjcBuild[89749:9420092] 延时1s的任务2--<NSThread: 0x600001714300>{number = 2, name = (null)}

截屏2024-10-18 上午1.37.55.png

结论:由于并发队列异步执行任务是乱序执行完毕的,所以使用栅栏函数可以很好的控制队列内任务执行的顺序

dis patch_barrier_sync & dispatch_barrier_async区别

  • dispatch_barrier_async:前面的任务执行完毕才会来到这里
  • dispatch_barrier_sync:作用相同,但是这个会堵塞线程,影响后面的任务执行

将案例二中的dispatch_barrier_async改成dispatch_barrier_sync

2023-03-23 14:40:43.658257+0800 KCObjcBuild[89789:9421485] 开始--<_NSMainThread: 0x60000170c180>{number = 1, name = main}
2023-03-23 14:40:43.659020+0800 KCObjcBuild[89789:9421485] 第一次结束--<_NSMainThread: 0x60000170c180>{number = 1, name = main}
2023-03-23 14:40:45.659136+0800 KCObjcBuild[89789:9421532] 延时2s的任务1--<NSThread: 0x6000017103c0>{number = 2, name = (null)}
2023-03-23 14:40:45.659478+0800 KCObjcBuild[89789:9421485] ---栅栏任务---
2023-03-23 14:40:45.659836+0800 KCObjcBuild[89789:9421485] 栅栏结束--<_NSMainThread: 0x60000170c180>{number = 1, name = main}
2023-03-23 14:40:45.660018+0800 KCObjcBuild[89789:9421485] 第二次结束--<_NSMainThread: 0x60000170c180>{number = 1, name = main}
2023-03-23 14:40:49.451942+0800 KCObjcBuild[89789:9421532] 延时1s的任务2--<NSThread: 0x6000017103c0>{number = 2, name = (null)}

结论:dispatch_barrier_async可以控制队列中任务的执行顺序,而dispatch_barrier_sync不仅阻塞了队列的执行,也阻塞了线程的执行(尽量少用)

栅栏函数注意点

  1. 尽量使用自定义的并发队列

    • 使用全局队列起不到栅栏函数的作用
    • 使用全局队列时由于对全局队列造成堵塞,可能致使系统其他调用全局队列的地方也堵塞从而导致崩溃(并不是只有你在使用这个队列)
  2. 栅栏函数只能控制同一并发队列:打个比方,平时在使用AFNetworking做网络请求时为什么不能用栅栏函数起到同步锁堵塞的效果,因为AFNetworking内部有自己的队列

dispatch_semaphore_t

应用场景:同步当锁, 控制GCD最大并发数

  • dispatch_semaphore_create():创建信号量
  • dispatch_semaphore_wait():等待信号量,信号量减1。当信号量< 0时会阻塞当前线程,根据传入的等待时间决定接下来的操作——如果永久等待将等到信号(signal)才执行下去
  • dispatch_semaphore_signal():释放信号量,信号量加1。当信号量>= 0 会执行wait之后的代码

下面这段代码要求使用信号量来按序输出(当然栅栏函数可以满足要求)

#import <Foundation/Foundation.h> 

int main(int argc, const char * argv[]) { 
    @autoreleasepool {
        dispatch_queue_t queue = dispatch_queue_create("CJ", DISPATCH_QUEUE_CONCURRENT);
        for (int i = 0; i < 10; i++) {
            dispatch_async(queue, ^{
                NSLog(@"当前%d----线程%@", i, [NSThread currentThread]);
            });
            NSLog(@"当前%d完成",i);
            // 使用栅栏函数
            dispatch_barrier_async(queue, ^{
                NSLog(@"栅栏%d----线程%@", i, [NSThread currentThread]);
            });
        }
        
        //NSLog(@"main主线程: %@", [NSThread currentThread]);
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode: NSRunLoopCommonModes];
        [[NSRunLoop currentRunLoop] run];
    }
    return 0;
}

利用信号量的API来进行代码改写

#import <Foundation/Foundation.h> 

int main(int argc, const char * argv[]) { 
    @autoreleasepool {
         dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
        dispatch_queue_t queue = dispatch_queue_create("CJ", DISPATCH_QUEUE_CONCURRENT);
        for (int i = 0; i < 10; i++) {
            dispatch_async(queue, ^{
                NSLog(@"当前%d----线程%@", i, [NSThread currentThread]);
                // 打印任务结束后信号量解锁
                dispatch_semaphore_signal(semaphore);
            });
            NSLog(@"当前%d完成",i);
//            // 使用栅栏函数
//            dispatch_barrier_sync(queue, ^{
//                NSLog(@"栅栏%d----线程%@", i, [NSThread currentThread]);
//            });
            // 由于一步执行,打印任务比较慢,所以这里信号量加锁
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        }
        
        //NSLog(@"main主线程: %@", [NSThread currentThread]);
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode: NSRunLoopCommonModes];
        [[NSRunLoop currentRunLoop] run];
    }
    return 0;
}
  • lldb打印:
2023-03-23 15:06:35.022332+0800 KCObjcBuild[90372:9441970] 当前0完成
2023-03-23 15:06:35.022565+0800 KCObjcBuild[90372:9442039] 当前0----线程<NSThread: 0x600001704400>{number = 2, name = (null)}
2023-03-23 15:06:35.022786+0800 KCObjcBuild[90372:9441970] 当前1完成
2023-03-23 15:06:35.022937+0800 KCObjcBuild[90372:9442039] 当前1----线程<NSThread: 0x600001704400>{number = 2, name = (null)}
2023-03-23 15:06:35.023094+0800 KCObjcBuild[90372:9441970] 当前2完成
2023-03-23 15:06:35.023150+0800 KCObjcBuild[90372:9442039] 当前2----线程<NSThread: 0x600001704400>{number = 2, name = (null)}
2023-03-23 15:06:35.023308+0800 KCObjcBuild[90372:9441970] 当前3完成
2023-03-23 15:06:35.023406+0800 KCObjcBuild[90372:9442039] 当前3----线程<NSThread: 0x600001704400>{number = 2, name = (null)}
2023-03-23 15:06:35.023508+0800 KCObjcBuild[90372:9441970] 当前4完成
2023-03-23 15:06:35.023558+0800 KCObjcBuild[90372:9442039] 当前4----线程<NSThread: 0x600001704400>{number = 2, name = (null)}
2023-03-23 15:06:35.023694+0800 KCObjcBuild[90372:9441970] 当前5完成
2023-03-23 15:06:35.023720+0800 KCObjcBuild[90372:9442039] 当前5----线程<NSThread: 0x600001704400>{number = 2, name = (null)}
2023-03-23 15:06:35.023849+0800 KCObjcBuild[90372:9441970] 当前6完成
2023-03-23 15:06:35.023889+0800 KCObjcBuild[90372:9442039] 当前6----线程<NSThread: 0x600001704400>{number = 2, name = (null)}
2023-03-23 15:06:35.024035+0800 KCObjcBuild[90372:9441970] 当前7完成
2023-03-23 15:06:35.024062+0800 KCObjcBuild[90372:9442039] 当前7----线程<NSThread: 0x600001704400>{number = 2, name = (null)}
2023-03-23 15:06:35.024193+0800 KCObjcBuild[90372:9441970] 当前8完成
2023-03-23 15:06:35.024202+0800 KCObjcBuild[90372:9442039] 当前8----线程<NSThread: 0x600001704400>{number = 2, name = (null)}
2023-03-23 15:06:35.024348+0800 KCObjcBuild[90372:9441970] 当前9完成
2023-03-23 15:06:35.024419+0800 KCObjcBuild[90372:9442039] 当前9----线程<NSThread: 0x600001704400>{number = 2, name = (null)}

如果当创建信号量时传入值为1又会怎么样呢?

  • i=0时有可能先打印,也可能会先发出wait信号量-1,但是wait之后信号量为0不会阻塞线程,所以进入i=1
  • i=1时有可能先打印,也可能会先发出wait信号量-1,但是wait之后信号量为-1阻塞线程,等待signal再执行下去
2023-03-23 15:08:31.598926+0800 KCObjcBuild[90417:9443749] 当前0完成
2023-03-23 15:08:31.599179+0800 KCObjcBuild[90417:9443749] 当前1完成
2023-03-23 15:08:31.599177+0800 KCObjcBuild[90417:9443798] 当前0----线程<NSThread: 0x60000170c5c0>{number = 2, name = (null)}
2023-03-23 15:08:31.599465+0800 KCObjcBuild[90417:9443749] 当前2完成
2023-03-23 15:08:31.599474+0800 KCObjcBuild[90417:9443798] 当前2----线程<NSThread: 0x60000170c5c0>{number = 2, name = (null)}
2023-03-23 15:08:31.599500+0800 KCObjcBuild[90417:9443799] 当前1----线程<NSThread: 0x600001708040>{number = 3, name = (null)}
2023-03-23 15:08:31.599647+0800 KCObjcBuild[90417:9443749] 当前3完成
2023-03-23 15:08:31.599700+0800 KCObjcBuild[90417:9443799] 当前3----线程<NSThread: 0x600001708040>{number = 3, name = (null)}
2023-03-23 15:08:31.599758+0800 KCObjcBuild[90417:9443749] 当前4完成
2023-03-23 15:08:31.599806+0800 KCObjcBuild[90417:9443798] 当前4----线程<NSThread: 0x60000170c5c0>{number = 2, name = (null)}
2023-03-23 15:08:31.599907+0800 KCObjcBuild[90417:9443749] 当前5完成
2023-03-23 15:08:31.599954+0800 KCObjcBuild[90417:9443799] 当前5----线程<NSThread: 0x600001708040>{number = 3, name = (null)}
2023-03-23 15:08:31.600024+0800 KCObjcBuild[90417:9443749] 当前6完成
2023-03-23 15:08:31.600063+0800 KCObjcBuild[90417:9443798] 当前6----线程<NSThread: 0x60000170c5c0>{number = 2, name = (null)}
2023-03-23 15:08:31.600189+0800 KCObjcBuild[90417:9443799] 当前7----线程<NSThread: 0x600001708040>{number = 3, name = (null)}
2023-03-23 15:08:31.600146+0800 KCObjcBuild[90417:9443749] 当前7完成
2023-03-23 15:08:31.600565+0800 KCObjcBuild[90417:9443749] 当前8完成
2023-03-23 15:08:31.600645+0800 KCObjcBuild[90417:9443798] 当前8----线程<NSThread: 0x60000170c5c0>{number = 2, name = (null)}
2023-03-23 15:08:31.600719+0800 KCObjcBuild[90417:9443749] 当前9完成
2023-03-23 15:08:31.600780+0800 KCObjcBuild[90417:9443799] 当前9----线程<NSThread: 0x600001708040>{number = 3, name = (null)}

结论:

  • 创建信号量时传入值为1时,可以通过两次才堵塞
  • 传入值为2时,可以通过三次才堵塞

dispatch_source

应用场景:GCDTimer

定义及使用

dispatch_source是一种基本的数据类型,可以用来监听一些底层的系统事件

  • Timer Dispatch Source:定时器事件源,用来生成周期性的通知或回调
  • Signal Dispatch Source:监听信号事件源,当有UNIX信号发生时会通知
  • Descriptor Dispatch Source:监听文件或socket事件源,当文件或socket数据发生变化时会通知
  • Process Dispatch Source:监听进程事件源,与进程相关的事件通知
  • Mach port Dispatch Source:监听Mach端口事件源
  • Custom Dispatch Source:监听自定义事件源

主要使用的API:

  • dispatch_source_create: 创建事件源
  • dispatch_source_set_event_handler: 设置数据源回调
  • dispatch_source_merge_data: 设置事件源数据
  • dispatch_source_get_data: 获取事件源数据
  • dispatch_resume: 继续
  • dispatch_suspend: 挂起
  • dispatch_cancle: 取消

自定义定时器

iOS开发中一般使用NSTimer来处理定时逻辑,但NSTimer是依赖Runloop的,而Runloop可以运行在不同的模式下。如果NSTimer添加在一种模式下,当Runloop运行在其他模式下的时候,定时器就挂机了;又如果Runloop在阻塞状态,NSTimer触发时间就会推迟到下一个Runloop周期。因此NSTimer在计时上会有误差,并不是特别精确,而GCD定时器不依赖Runloop,计时精度要高很多

#import <Foundation/Foundation.h> 

int main(int argc, const char * argv[]) { 
    @autoreleasepool {
        // 1.创建队列
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        // 2.创建timer
        dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
        // 3.设置timer首次执行时间,间隔,精确度
        dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);
        // 4.设置timer事件回调
        dispatch_source_set_event_handler(timer, ^{
            NSLog(@"GCDTimer---当前线程:%@", [NSThread currentThread]);
        });
        // 5.默认是挂起状态,需要手动激活
        dispatch_resume(timer);
        
        //NSLog(@"main主线程: %@", [NSThread currentThread]);
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode: NSRunLoopCommonModes];
        [[NSRunLoop currentRunLoop] run];
    }
    return 0;
}
  • lldb:
2023-03-23 15:36:17.841263+0800 KCObjcBuild[90968:9462593] GCDTimer---当前线程:<NSThread: 0x600001710140>{number = 2, name = (null)}
2023-03-23 15:36:19.916774+0800 KCObjcBuild[90968:9462593] GCDTimer---当前线程:<NSThread: 0x600001710140>{number = 2, name = (null)}
2023-03-23 15:36:21.917735+0800 KCObjcBuild[90968:9462593] GCDTimer---当前线程:<NSThread: 0x600001710140>{number = 2, name = (null)}
2023-03-23 15:36:23.928318+0800 KCObjcBuild[90968:9462593] GCDTimer---当前线程:<NSThread: 0x600001710140>{number = 2, name = (null)}

使用dispatch_source自定义定时器注意点:

  • GCDTimer需要强持有,否则出了作用域立即释放,也就没有了事件回调
  • GCDTimer默认是挂起状态,需要手动激活
  • GCDTimer没有repeat,需要封装来增加标志位控制
  • GCDTimer如果存在循环引用,使用weak+strong或者提前调用dispatch_source_cancel取消timer
  • dispatch_resumedispatch_suspend调用次数需要平衡
  • source挂起状态下,如果直接设置source = nil或者重新创建source都会造成crash。正确的方式是在激活状态下调用dispatch_source_cancel(source)释放当前的source

GCD-API总结

API内容说明
dispatch_sync()同步执行
dispatch_async()异步执行
dispatch_queue_create()创建队列
dispatch_get_main_queue()获取主队列
dispatch_get_global_queue()获取全局队列
dispatch_after()延时执行
dispatch_once()一次性执行
dispatch_apply()提交队列
dispatch_group_create()创建调度组
dispatch_group_async()执行进组任务
dispatch_group_enter()/ dispatch_group_leave()将调度组中的任务未执行完毕的任务数目加减1 (两个函数要配合使用)
dispatch_group_wait()设置等待时间(成功为0)
dispatch_barrier_sync()同步栅栏函数
dispatch_barrier_async()异步栅栏函数
dispatch_group_notify()监听队列组执行完毕
dispatch_semaphore_creat()创建信号量
dispatch_semaphore_wait()等待信号量
dispatch_semaphore_signal()释放信号量
dispatch_source_create创建源
dispatch_source_set_event_handler设置源事件回调
dispatch_source_merge_data源事件设置数据
dispatch_source_get_data获取源事件数据
dispatch_resume继续
dispatch_suspend挂起
dispatch_cancle取消