多线程在iOS
中有着举足轻重的地位,了解线程的工作原理,有助于提升app
的质量与优化app
性能.
一、进程、线程与队列
进程的定义
- 进程是指在系统中正在运行的一个应用程序,如微信、支付宝app都是一个进程
- 每个进程之间是独立的,每个进程均运行在其专用的且受保护的内存
线程的定义
- 线程是进程的基本执行单元,一个进程的所有任务都在线程中执行
- 进程想要执行任务,必须得有线程,进程至少要有一条线程
- 程序启动会默认开启一条线程,这条线程被称为主线程或
UI
线程
进程与线程的关系和区别
-
地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间
-
资源拥有:同一进程内的线程共享本进程的资源如内存、
I/O
、cpu
等,但是进程之间的资源是独立的 -
一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉,所以多进程要比多线程健壮
-
进程切换时,消耗的资源大、效率高。所以设计到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程而不能用进程
-
执行过程:每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制
-
线程是处理器调度的基本单位,但进程不是
可能会觉得这些理论知识很抽象,百度出来一大堆但是都不好理解,看完下面的理解就全明白了
进程与线程的关系图
可以把iOS
系统想象成商场,进程则是商场中的店铺,线程是店铺雇佣的员工:
-
进程之间的相互独立
- 奶茶店看不到果汁店的账目(访问不了别的进程的内存)
- 果汁店用不了奶茶店的波霸(进程之间的资源是独立的)
-
进程至少要有一条线程
- 店铺至少要有一个员工(进程至少有一个线程)
- 早上开店门的员工(相当于主线程)
-
进程/线程崩溃的情况
- 奶茶店倒闭了并不会牵连果汁店倒闭(进程崩溃不会对其他进程产生影响)
- 奶茶店的收银员不干了会导致奶茶店无法正常运作(线程崩溃导致进程瘫痪)
移动开发不一定是单进程处理的,
android
就是多进程处理的;而iOS
采用沙盒机制,这也是苹果运行能够流畅安全的一个主要原因
队列的定义
队列,又称为伫列(queue
),是先进先出(FIFO: First-In-First-Out
)的线性表,在具体应用中通常用链表或者数组来实现。装载线程任务的队形结构。队列只允许在后端(称为rear
)进行插入操作,在前端(称为front
)进行删除操作。队列的操作方式和堆栈类似,唯一的区别在于队列只允许新数据在后端进行添加。
队列和线程的关系
两者是没有关系的,可以这么理解:
- 队列负责调度任务,线程执行任务
- 在银行(进程)中,有4个工作窗口(线程),而只有一条队伍(队列)
- 窗口(线程)只负责为排队的人办理业务,并不会管队伍(队列)是怎么排的
线程和runloop
的关系
-
runloop
与线程是一一对应的。一个runloop
对应一个核心的线程,为什么说是核心的,是因为runloop
是可以嵌套的,但是核心的只能有一个,他们的关系保存在一个全局的字典里 -
runloop
是来管理线程的。当线程的runloop
被开启后,线程会在执行完任务后进入休眠状态,有了任务就会被唤醒去执行任务 -
runloop
在第一次获取时被创建,在线程结束时被销毁-
对于主线程来说,
runloop
在程序一启动就默认创建好了 -
对于子线程来说,
runloop
是懒加载的,只有当我们使用的时候才会创建,所以在子线程用定时器要注意:确保子线程的runloop
被创建,不然定时器不会回调。
-
影响任务执行速度的因素
以下因素都会对任务的执行速度造成影响:
-
cpu
的调度
-
- 线程的执行速率
-
- 队列情况
-
- 任务执行的复杂度
-
- 任务的优先级
二、多线程
多线程原理
- 同一时间,
CPU
只能处理一条线程,只有一条线程在工作(执行)。 - 多线程并发(同时)执行,其实就是
CPU
执行快速地在多条线程之间调度(切换)。
多线程意义
-
优点
- 能适当提高程序的执行效率
- 能适当提高资源的利用率(
CPU
、内存) - 线程上的任务执行完成后,线程会自动销毁
-
缺点
- 开启线程需要占用一定的内存空间(默认情况下,每一个线程都占
512KB
,创建线程大约需要90
毫秒的创建时间) - 如果开启大量的线程,会占用大量的内存空间,降低程序的性能
- 线程越多,
CPU
在调用线程上的开销就越大 - 程序设计更加复杂,比如线程间的通信、多线程的数据共享
- 开启线程需要占用一定的内存空间(默认情况下,每一个线程都占
多线程生命周期
多线程的生命周期是:新建 - 就绪 - 运行 - 阻塞 - 死亡
- 新建:实例化线程对象
- 就绪:向线程对象发送
start
消息,线程对象被加入可调度线程池等待CPU
调度。 - 运行:
CPU
负责调度可调度线程池中线程的执行。线程执行完成之前,状态可能会在就绪和运行之间来回切换。就绪和运行之间的状态变化由CPU
负责,程序员不能干预。 - 阻塞:当满足某个预定条件时,可以使用休眠或锁,阻塞线程执行。
sleepForTimeInterval
(休眠指定时长),sleepUntilDate
(休眠到指定日期),@synchronized(self):
(互斥锁)。 - 死亡:正常死亡,线程执行完毕。非正常死亡,当满足某个条件后,在线程内部中止执行/在主线程中止线程对象
线程池的原理
-
若线程池大小小于核心线程池大小时
- 创建线程执行任务
-
若线程池大小大于等于核心线程池大小时
- 先判断线程池工作队列是否已满
- 若没满就将任务
push
进队列 - 若已满时,且
maximumPoolSize>corePoolSize
,将创建新的线程来执行任务 - 反之则交给饱和策略去处理
参数名 | 代表意义 |
---|---|
corePoolSize | 线程池的基本大小(核心线程池大小) |
maximumPool | 线程池的最大大小 |
keepAliveTime | 线程池中超过corePoolSize树木的空闲线程的最大存活时间 |
unit | keepAliveTime参数的时间单位 |
workQueue | 任务阻塞队列 |
threadFactory | 新建线程的工厂 |
handler | 当提交的任务数超过maxmumPoolSize与workQueue之和时, 任务会交给RejectedExecutionHandler来处理 |
饱和策略有如下四个:
-
AbortPolicy
直接抛出RejectedExecutionExeception
异常来阻止系统正常运行
-
CallerRunsPolicy
将任务回退到调用者
-
DisOldestPolicy
丢掉等待最久的任务
-
DisCardPolicy
直接丢弃任务
多线程实现方案
技术方案 | 简介 | 语言 | 线程生命周期 | 使用评率 |
---|---|---|---|---|
pthread | 一套通用的多线程API 适用于Unix/Linux/Windows等系统 跨平台/可移植 使用难度大 | C | 程序员管理 | 几乎不用 |
NSThread | 使用更加面向对象 简单易用,可直接操作线程对象 | OC | 程序员管理 | 偶尔使用 |
GCD | 旨在替代NSThread等线程技术 充分利用设备的多核 | C | 自动管理 | 经常使用 |
NSOperation | 基于GCD(底层是GCD) 比GCD多了一些更简单实用的功能 使用更加面向对象 | OC | 自动管理 | 经常使用 |
GCD
和NSOperation
的区别
-
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
的优势:
-
GCD
是苹果公司为多核的并行运算提出的解决方案
-
GCD
会自动利用更多的CPU
内核(比如双核、四核)
-
GCD
会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
-
- 程序员只需要告诉
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源码上,这个部分是个masOC
的Command 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
任务 - 异步是多线程的代名词
- 只需要将之前代码的
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
多线程中队列分为串行队列(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_HIGH
、DISPATCH_QUEUE_PRIORITY_DEFAULT
、DISPATCH_QUEUE_PRIORITY_LOW
、DISPATCH_QUEUE_PRIORITY_BACKGROUND
- 第一个参数是优先级枚举值,默认优先级为
- 最简单的是使用
串行/并发和同步/异步的排列组合
主队列和全局队列单独考虑,组合结果以总结表格为准
串行+同步
任务一个接一个执行,不开辟线程
- 测试代码:
#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_notify
在dispatch_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_enter
和dispatch_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
应用场景:同步锁
前文已经提过并发执行异步队列
会开辟线程,而任务也会因为任务复杂度和cpu的调度导致各个乱序执行完毕,比如上图中的任务3
明明是先于任务4
执行,但是晚于任务4
执行完毕
此时GCD就提供了两个API——dispatch_barrier_sync
和dispatch_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
结论:由于串行队列异步执行
任务是一个接一个执行完毕的,所以使用栅栏函数没意义
并发队列使用栅栏函数
#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)}
结论:由于并发队列异步执行
任务是乱序执行完毕的,所以使用栅栏函数可以很好的控制队列内任务执行的顺序
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
不仅阻塞了队列的执行,也阻塞了线程的执行(尽量少用)
栅栏函数注意点
-
尽量使用自定义的并发队列:
- 使用全局队列起不到栅栏函数的作用
- 使用全局队列时由于对全局队列造成堵塞,可能致使系统其他调用全局队列的地方也堵塞从而导致崩溃(并不是只有你在使用这个队列)
-
栅栏函数只能控制同一并发队列:打个比方,平时在使用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_resume
和dispatch_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 | 取消 |