当前位置:网站首页>GCD简单了解
GCD简单了解
2022-07-31 09:41:00 【kicinio】
一:基础名称
GCD的创建依赖于任务
与队列
这两个概念。
任务就是block内执行的操作,block内调用的某个方法。任务有两种方式,一为同步执行
,二为异步执行
。二者的区别在于是否具备开启子线程的能力,执行的任务在队列中执行的方式(顺序)。
同步执行的特点
同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行。
只能在当前线程中执行任务,不具备开启新线程的能力。
异步执行的特点:
异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。
可以在新的线程中执行任务,具备开启新线程的能力。
可以看出,同步执行一定是在当前指定线程中执行,而异步虽然具备开启子线程的能力,具体还要与指定的队列相关。
队列的结构形式即数据结构中的队列一样,遵循先进先出的原则。即一个队列中的任务第一个被追加到队列中,则它应该第一个被完成释放。而GCD中存在着两种队列,一为串行队列
,二为并行队列
。二者的区别在于执行顺序与开启的线程所在位置不一样。
串行队列的特点:
“串”即为任务在当前轴上挨次执行,上一个任务执行完毕,下一个任务开始执行。
只开启一个线程。
并行队列的特点:
多个任务在此队列里面同时执行,是为“并发”。
在异步执行的方式下可开启多个线程同时执行。
然而,在GCD里面还存在着两个特殊的队列,即属于串行队列的主队列
,属于并发队列的全局并发队列
。主队列属于串行队列,但是iOS中程序是在主队列里面执行就造就了“主队列”的特殊。
1.1 dispatch_queue_t
该函数用以创建队列对象,可为串行队列,并发队列,主队列,串行队列
1.2 dispatch_group_t
该函数用以创建group组队列
1.3 dispatch_group_async
该函数根据指定的队列,去异步执行
1.4 dispatch_group_enter
该函数标志着一个任务追加到group,执行一次,即group中未执行完毕的任务数+1
1.5 dispatch_group_leave
该函数标志着一个任务已经被完成,需要提醒group移除一个任务数,即group中未执行完毕的任务数-1
1.6 dispatch_group_notify
当group中任务数为0时,会调用notify执行block,常用于回到主线程,即解除了dispatch_group_wait
1.7 dispatch_semaphore_wait
该函数使得被调用的线程加锁,当semaphore信号量小于0时被阻塞
1.8 dispatch_semaphore_signal
该函数相当于被调用线程结束,会使得semaphore信号量+1
二: 排列方式
下表为常见的排列方式
并发队列 | 串行队列 | 主队列 | |
---|---|---|---|
同步执行 | 未开启子线程;串行执行任务 | 未开启子线程;串行地执行任务 | 死锁 |
异步执行 | 开启了子线程;任务并行地执行 | 开启了子线程;串行执行任务 | 未开启子线程;任务串行地执行 |
2.1 同步 + 并发队列
说明:
该组合会在当前线程执行,不会开启新的线程,因为同步执行没有开启子线程的能力。
虽然队列指定为并发队列,但是执行方式为同步执行,同步执行不具备开启线程的能力,故任务只能在当前线程以串行方式一个接一个地执行。
可以确定该组合的代码执行顺序是从上至下的。
2.2 异步 + 并发队列
说明:
该组合会开启新的线程执行。因为异步具备开启新线程的能力,而并发队列确定了开启新线程的个数。
由于该组合是以异步方式执行,而该组合(编写的方法)内可能会存在异步执行队列之前的代码,这些代码会和异步队列同时/交替执行。
2.3 同步 + 串行队列
说明:
该组合不会开启新的线程去执行,因为执行方式是同步执行,该方式不具备开启新线程的能力;而串行队列决定了线程个数只有一个,故任务会挨个执行。
该组合(方法)内执行的代码顺序是从上至下的,但完成的进度依赖于任务本身的复杂度(耗时性)。
2.4 异步 + 串行队列
说明:
该组合会开启新的线程,但只会开启一个。但由于队列为串行队列,故只会在开启的新线程里挨个执行这些任务。
该组合(方法)内执行的顺序是从上至下的。即,组合内的非新线程代码和新线程代码同时执行。
2.5 同步 + 主队列
说明:
这种组合方式如果以方法形式直接在主线程里面调用会造成死锁。即主线程里面等待方法执行完毕,而方法里面的任务等待主线程处理完方法,二者互相等待,直接死锁。
但如果在组合形式是以子线程形式调用,则不会发生死锁。原因即:新开启的子线程等待方法执行完毕,而方法内部的任务是在主线程内执行,待主线程内的任务执行完毕,新开启的子线程也执行完毕,也不会发生死锁。
2.6 异步 + 主队列
说明:
不会开启新的线程。虽然异步具备开启新线程的能力,但队列为主队列,只能在此执行,故异步内的任务均在主队列里面执行。
主队列为运行在主线程的串行队列,任务挨个执行,也即异步任务在此会挨个执行。
三:常见异步业务场景与编码
3.1 异步线程通信
异步线程通信的业务场景处处可见,以最常见的网络请求为例,我们通常会开启一个子线程用以请求网络,请求完毕后拿到请求数据回到主线程,供主线程刷新UI。
/** 线程间通信 - 在全局并发队列执行任务,执行完毕后会到主线程操作数据 */
- (void)asyncDoAndAppendToMainQueue{
dispatch_queue_t global_concurrent_queue = dispatch_queue_create("DIS", DISPATCH_QUEUE_PRIORITY_DEFAULT);
dispatch_queue_t main_queue = dispatch_get_main_queue();
dispatch_async(global_concurrent_queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"------> THREAD: %@", [NSThread currentThread]);
NSInteger a = 1;
a++;
dispatch_async(main_queue, ^{
NSLog(@"------> value: %d", a);
NSLog(@"------> THREAD: %@", [NSThread currentThread]);
});
});
}
代码说明:首先创建一个全局队列,接着定义一个主队列。在dispatch_async函数里面传入全局队列用以发起网络请求。在此模拟网络请求过程,耗时两秒使得a++,然后异步回到主线层并打印数据。结果如下:
2022-03-15 15:47:47.819016+0800 GCDios[17679:8316624] ------> THREAD: <NSThread: 0x2812ac380>{
number = 9, name = (null)}
2022-03-15 15:47:47.819246+0800 GCDios[17679:8316613] ------> value: 2
2022-03-15 15:47:47.819345+0800 GCDios[17679:8316613] ------> THREAD: <NSThread: 0x2812fc900>{
number = 1, name = main}
3.2 异步线程组合请求并回调通知
有时候一个VC或者模块发起的并不单单一个请求,而是多个请求,如何将多个请求结果包装为一个统一回调参数或对象是个问题,使用异步组合请求可以很好地解决(出现这样的业务场景是因为这个VC或模块在初始化的时候需要多个参数或特殊的模型,而初始化仅有一次,不可能在后续中再次请求刷新)。
/** 异步执行全局并发队列,enter leave后notify回到主线程 */
- (void)asyncDoGroupWithEnterLeaveAndAppendToMainQueue{
dispatch_queue_t global_concurrent_queue = dispatch_queue_create("DIS", DISPATCH_QUEUE_PRIORITY_DEFAULT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, global_concurrent_queue, ^{
NSInteger a = 0;
a++;
// 未完成任务数+1,标志着notify不会被调用
dispatch_group_enter(group);
[NSThread sleepForTimeInterval:2];
NSLog(@"------> value: %d", a);
NSLog(@"------> THREAD: %@\n", [NSThread currentThread]);
// 该耗时任务已完成,应该从group队列里面移除
dispatch_group_leave(group);
// 再次放入一个任务到group队列里
dispatch_group_enter(group);
[NSThread sleepForTimeInterval:2];
a++;
NSLog(@"------> value: %d", a);
NSLog(@"------> THREAD: %@\n", [NSThread currentThread]);
// 任务结束,从group立main移除,则会通知notify调用
dispatch_group_leave(group);
// 将group队列任务操纵的数据带回到主线程
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"------> value: %d", a);
NSLog(@"------> THREAD: %@\n", [NSThread currentThread]);
});
});
}
可以看到,刚开始的时候group内任务数为0,然后调用dispatch_group_enter(group)
,使得该group内任务数+1,而group内任务数为非0的情况下是不会执行dispatch_group_notify
方法的。当第一个模拟网络请求结束时,调用dispatch_groyp_leave(group)
使得任务数-1以便执行下一个模拟网络请求。待第二个网络请求结束后,group内任务数为0,此时便执行了dispatch_group_notify
方法回到主线程。如果我们这个示例方法内包含一个block参数,并在notify内回调,那么外层模块就会等到所有请求结束后才能拿到参数或模型。
结果如下:
2022-03-15 15:54:35.889857+0800 GCDios[17681:8317881] ------> value: 1
2022-03-15 15:54:35.890880+0800 GCDios[17681:8317881] ------> THREAD: <NSThread: 0x281fa0680>{
number = 8, name = (null)}
2022-03-15 15:54:37.896368+0800 GCDios[17681:8317881] ------> value: 2
2022-03-15 15:54:37.897212+0800 GCDios[17681:8317881] ------> THREAD: <NSThread: 0x281fa0680>{
number = 8, name = (null)}
2022-03-15 15:54:37.898013+0800 GCDios[17681:8317865] ------> value: 2
2022-03-15 15:54:37.898510+0800 GCDios[17681:8317865] ------> THREAD: <NSThread: 0x281ff02c0>{
number = 1, name = main}
3.3 异步任务通过dispatch_semaphore转同步
有时候业务为执行一个异步操作,但需要该异步操作的结果以供下面的代码调用,这就是异步任务转同步任务。
/** 异步任务通过semaphore转同步任务 */
- (void)asyncTransferSyncBySemaphore{
// 1. 当前为主线程
NSLog(@"------> currentThread: %@", [NSThread currentThread]);
// 2. 创建全局队列用以执行子线程任务
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 3. 创建semaphore为0的信号量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
// 4. 定义资源
__block NSInteger a = 20;
dispatch_async(queue, ^{
// 4.2 模拟耗时操作
[NSThread sleepForTimeInterval:2];
NSLog(@"------> Thread: %@", [NSThread currentThread]);
a++;
// 4.4 此时主线程已经卡住,执行这一步后semaphore+1,总和为0,主线程恢复
dispatch_semaphore_signal(semaphore);
});
// 4.1 上述子线程与这里的主线程会同步执行,但是子线程里和耗时操作尚未操纵资源a,此时a为20
NSLog(@"------> Before wait Thread: %@ AND PARA A:%d", [NSThread currentThread], a);
// 4.3 信号量-1,主线程堵塞,当过不了多久子线程里耗时操作就会完成,其后操纵资源a,并使semaphore+1,使主线程开放
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
// 4.5 主线程通路后资源a已被子线程通过耗时操作操纵,此时a为21
NSLog(@"------> After wait Thread: %@ AND PARA A:%d", [NSThread currentThread], a);
}
如果最后一个Log语句打印的参数a为21,即证明了我们成功地将异步任务转换为了同步任务,而下面的结果也恰恰佐证了这一方法。
结果如下:
2022-03-15 16:55:45.889530+0800 GCDios[17704:8329687] ------> currentThread: <NSThread: 0x2818ac900>{
number = 1, name = main}
2022-03-15 16:55:45.889676+0800 GCDios[17704:8329687] ------> Before wait Thread: <NSThread: 0x2818ac900>{
number = 1, name = main} AND PARA A:20
2022-03-15 16:55:47.894895+0800 GCDios[17704:8329702] ------> Thread: <NSThread: 0x2818e9f80>{
number = 6, name = (null)}
2022-03-15 16:55:47.895247+0800 GCDios[17704:8329687] ------> After wait Thread: <NSThread: 0x2818ac900>{
number = 1, name = main} AND PARA A:21
3.4 异步线程安全之加锁解锁
竞争性的资源在多线程修改时永远不安全,可能会出现各式各样奇怪的问题。在此,模拟出一种常见的业务,使用semaphore信号量加锁来保证竞争资源的安全。
我们此时拥有车辆为20辆,拥有两家门店store_1_queue与store_2_queue,每家门店当顾客预订车辆时会使得carCount自减1,当carCount为0时不再提供预订服务,Log没有车辆。
/// 模拟门店预约车辆服务
- (void)bookCar{
NSLog(@"------> FIRST: %@ AT THREAD: %@", self.semaphore, [NSThread currentThread]);
while (1) {
if (self.carCount > 0) {
self.carCount--;
NSLog(@"------> Store: %@, Used Count: %d", [NSThread currentThread], self.carCount);
[NSThread sleepForTimeInterval:0.2];
} else {
NSLog(@"------> Store: %@ has no car.", [NSThread currentThread]);
break;
}
}
}
上面使用了self.semaphore与self.carCount,在viewDidLoad方法内不初始化一下即可,如下:
self.carCount = 20;
self.semaphore = dispatch_semaphore_create(1);
门店的代码如下:
- (void)threadSafe{
// 创建门店1、门店2串行队列用以提供购买方式
dispatch_queue_t store_1_queue = dispatch_queue_create("queue.dayueceng", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t store_2_queue = dispatch_queue_create("queue.changfenggongyuan", DISPATCH_QUEUE_SERIAL);
// 门店1异步执行订车服务
dispatch_async(store_1_queue, ^{
[self bookCar];
});
// 门店2异步执行订车服务
dispatch_async(store_2_queue, ^{
[self bookCar];
});
}
当在viewDidLoad内调用threadSafe方法看似没问题,每个门店在各自的线程去执行bookCar方法直至self.carCount为0然后Log没有车辆。但是问题严重在于每个门店线程可能在同一时刻访问self.carCount,即会出现store_1_queue预订的时候车辆总数为16,而接下来当store_2_queue预订的时候车辆总数为17,这明显不缝合逻辑的。因此需要使用semaphore信号量加锁,使得在同一时刻只有唯一一个线程去访问修改self.carCount。完整代码如下:
@interface ViewController ()
@property (nonatomic, assign) NSInteger carCount;
@property (nonatomic, strong) dispatch_semaphore_t semaphore;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self initData];
[self threadSafe];
}
- (void)initData{
self.carCount = 20;
self.semaphore = dispatch_semaphore_create(1);
}
/** 线程安全示例 - 异步访问竞争资源并更新 */
- (void)threadSafe{
// 创建门店1、门店2串行队列用以提供购买方式
dispatch_queue_t store_1_queue = dispatch_queue_create("queue.dayueceng", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t store_2_queue = dispatch_queue_create("queue.changfenggongyuan", DISPATCH_QUEUE_SERIAL);
// 门店1异步执行订车服务
dispatch_async(store_1_queue, ^{
[self bookCar];
});
// 门店2异步执行订车服务
dispatch_async(store_2_queue, ^{
[self bookCar];
});
}
/// 模拟门店预约车辆服务
- (void)bookCar{
// 对于每一个门店线程而言,其操纵预定的是同一个竞争资源self.carCount,只要保证在同一时刻只有唯一一个线程去访问修改self.carCount即可保证线程安全
// 则使用wait与singal保证某个门店线程在预订期间semaphore为唯一访问者即可。
NSLog(@"------> FIRST: %@ AT THREAD: %@", self.semaphore, [NSThread currentThread]);
while (1) {
// 加锁,semaphore
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"------> WAIT: %@ AT THREAD: %@", self.semaphore, [NSThread currentThread]);
if (self.carCount > 0) {
self.carCount--;
NSLog(@"------> Store: %@, Used Count: %d", [NSThread currentThread], self.carCount);
[NSThread sleepForTimeInterval:0.2];
} else {
NSLog(@"------> Store: %@ has no car.", [NSThread currentThread]);
dispatch_semaphore_signal(self.semaphore);
break;
}
// 解锁
dispatch_semaphore_signal(self.semaphore);
NSLog(@"------> SIGNAL: %@ AT THREAD: %@", self.semaphore, [NSThread currentThread]);
}
}
可以看到虽然每个门店线程都访问了bookCar方法,但是通过wait使得每个门店线程在访问期间从加锁-防止其他门店预订-本门店预订-解锁
这个流程一直走下去,直到无车辆可供调度。
结果如下:
2022-03-15 16:20:19.170210+0800 GCDios[17690:8322635] ------> FIRST: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d86ac0>{
number = 7, name = (null)}
2022-03-15 16:20:19.170332+0800 GCDios[17690:8322635] ------> WAIT: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d86ac0>{
number = 7, name = (null)}
2022-03-15 16:20:19.170381+0800 GCDios[17690:8322635] ------> Store: <NSThread: 0x280d86ac0>{
number = 7, name = (null)}, Used Count: 19
2022-03-15 16:20:19.170467+0800 GCDios[17690:8322634] ------> FIRST: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d84b00>{
number = 8, name = (null)}
2022-03-15 16:20:19.371599+0800 GCDios[17690:8322635] ------> SIGNAL: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d86ac0>{
number = 7, name = (null)}
2022-03-15 16:20:19.371779+0800 GCDios[17690:8322634] ------> WAIT: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d84b00>{
number = 8, name = (null)}
2022-03-15 16:20:19.371850+0800 GCDios[17690:8322634] ------> Store: <NSThread: 0x280d84b00>{
number = 8, name = (null)}, Used Count: 18
2022-03-15 16:20:19.572499+0800 GCDios[17690:8322635] ------> WAIT: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d86ac0>{
number = 7, name = (null)}
2022-03-15 16:20:19.572502+0800 GCDios[17690:8322634] ------> SIGNAL: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d84b00>{
number = 8, name = (null)}
2022-03-15 16:20:19.572929+0800 GCDios[17690:8322635] ------> Store: <NSThread: 0x280d86ac0>{
number = 7, name = (null)}, Used Count: 17
2022-03-15 16:20:19.778504+0800 GCDios[17690:8322635] ------> SIGNAL: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d86ac0>{
number = 7, name = (null)}
2022-03-15 16:20:19.778827+0800 GCDios[17690:8322634] ------> WAIT: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d84b00>{
number = 8, name = (null)}
2022-03-15 16:20:19.779001+0800 GCDios[17690:8322634] ------> Store: <NSThread: 0x280d84b00>{
number = 8, name = (null)}, Used Count: 16
2022-03-15 16:20:19.984579+0800 GCDios[17690:8322634] ------> SIGNAL: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d84b00>{
number = 8, name = (null)}
2022-03-15 16:20:19.984587+0800 GCDios[17690:8322635] ------> WAIT: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d86ac0>{
number = 7, name = (null)}
2022-03-15 16:20:19.985258+0800 GCDios[17690:8322635] ------> Store: <NSThread: 0x280d86ac0>{
number = 7, name = (null)}, Used Count: 15
2022-03-15 16:20:20.191370+0800 GCDios[17690:8322635] ------> SIGNAL: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d86ac0>{
number = 7, name = (null)}
2022-03-15 16:20:20.191516+0800 GCDios[17690:8322634] ------> WAIT: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d84b00>{
number = 8, name = (null)}
2022-03-15 16:20:20.192384+0800 GCDios[17690:8322634] ------> Store: <NSThread: 0x280d84b00>{
number = 8, name = (null)}, Used Count: 14
2022-03-15 16:20:20.393759+0800 GCDios[17690:8322634] ------> SIGNAL: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d84b00>{
number = 8, name = (null)}
2022-03-15 16:20:20.393768+0800 GCDios[17690:8322635] ------> WAIT: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d86ac0>{
number = 7, name = (null)}
2022-03-15 16:20:20.394415+0800 GCDios[17690:8322635] ------> Store: <NSThread: 0x280d86ac0>{
number = 7, name = (null)}, Used Count: 13
2022-03-15 16:20:20.597513+0800 GCDios[17690:8322635] ------> SIGNAL: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d86ac0>{
number = 7, name = (null)}
2022-03-15 16:20:20.597750+0800 GCDios[17690:8322634] ------> WAIT: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d84b00>{
number = 8, name = (null)}
2022-03-15 16:20:20.598420+0800 GCDios[17690:8322634] ------> Store: <NSThread: 0x280d84b00>{
number = 8, name = (null)}, Used Count: 12
2022-03-15 16:20:20.804358+0800 GCDios[17690:8322634] ------> SIGNAL: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d84b00>{
number = 8, name = (null)}
2022-03-15 16:20:20.804525+0800 GCDios[17690:8322635] ------> WAIT: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d86ac0>{
number = 7, name = (null)}
2022-03-15 16:20:20.805148+0800 GCDios[17690:8322635] ------> Store: <NSThread: 0x280d86ac0>{
number = 7, name = (null)}, Used Count: 11
2022-03-15 16:20:21.011005+0800 GCDios[17690:8322635] ------> SIGNAL: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d86ac0>{
number = 7, name = (null)}
2022-03-15 16:20:21.011204+0800 GCDios[17690:8322634] ------> WAIT: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d84b00>{
number = 8, name = (null)}
2022-03-15 16:20:21.011824+0800 GCDios[17690:8322634] ------> Store: <NSThread: 0x280d84b00>{
number = 8, name = (null)}, Used Count: 10
2022-03-15 16:20:21.217719+0800 GCDios[17690:8322634] ------> SIGNAL: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d84b00>{
number = 8, name = (null)}
2022-03-15 16:20:21.218465+0800 GCDios[17690:8322635] ------> WAIT: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d86ac0>{
number = 7, name = (null)}
2022-03-15 16:20:21.218938+0800 GCDios[17690:8322635] ------> Store: <NSThread: 0x280d86ac0>{
number = 7, name = (null)}, Used Count: 9
2022-03-15 16:20:21.424744+0800 GCDios[17690:8322635] ------> SIGNAL: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d86ac0>{
number = 7, name = (null)}
2022-03-15 16:20:21.424779+0800 GCDios[17690:8322634] ------> WAIT: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d84b00>{
number = 8, name = (null)}
2022-03-15 16:20:21.425369+0800 GCDios[17690:8322634] ------> Store: <NSThread: 0x280d84b00>{
number = 8, name = (null)}, Used Count: 8
2022-03-15 16:20:21.631025+0800 GCDios[17690:8322634] ------> SIGNAL: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d84b00>{
number = 8, name = (null)}
2022-03-15 16:20:21.631465+0800 GCDios[17690:8322635] ------> WAIT: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d86ac0>{
number = 7, name = (null)}
2022-03-15 16:20:21.631723+0800 GCDios[17690:8322635] ------> Store: <NSThread: 0x280d86ac0>{
number = 7, name = (null)}, Used Count: 7
2022-03-15 16:20:21.834804+0800 GCDios[17690:8322635] ------> SIGNAL: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d86ac0>{
number = 7, name = (null)}
2022-03-15 16:20:21.834888+0800 GCDios[17690:8322634] ------> WAIT: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d84b00>{
number = 8, name = (null)}
2022-03-15 16:20:21.835452+0800 GCDios[17690:8322634] ------> Store: <NSThread: 0x280d84b00>{
number = 8, name = (null)}, Used Count: 6
2022-03-15 16:20:22.041551+0800 GCDios[17690:8322634] ------> SIGNAL: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d84b00>{
number = 8, name = (null)}
2022-03-15 16:20:22.042263+0800 GCDios[17690:8322635] ------> WAIT: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d86ac0>{
number = 7, name = (null)}
2022-03-15 16:20:22.042735+0800 GCDios[17690:8322635] ------> Store: <NSThread: 0x280d86ac0>{
number = 7, name = (null)}, Used Count: 5
2022-03-15 16:20:22.248610+0800 GCDios[17690:8322635] ------> SIGNAL: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d86ac0>{
number = 7, name = (null)}
2022-03-15 16:20:22.248646+0800 GCDios[17690:8322634] ------> WAIT: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d84b00>{
number = 8, name = (null)}
2022-03-15 16:20:22.249263+0800 GCDios[17690:8322634] ------> Store: <NSThread: 0x280d84b00>{
number = 8, name = (null)}, Used Count: 4
2022-03-15 16:20:22.455297+0800 GCDios[17690:8322634] ------> SIGNAL: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d84b00>{
number = 8, name = (null)}
2022-03-15 16:20:22.455332+0800 GCDios[17690:8322635] ------> WAIT: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d86ac0>{
number = 7, name = (null)}
2022-03-15 16:20:22.455917+0800 GCDios[17690:8322635] ------> Store: <NSThread: 0x280d86ac0>{
number = 7, name = (null)}, Used Count: 3
2022-03-15 16:20:22.661461+0800 GCDios[17690:8322634] ------> WAIT: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d84b00>{
number = 8, name = (null)}
2022-03-15 16:20:22.661466+0800 GCDios[17690:8322635] ------> SIGNAL: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d86ac0>{
number = 7, name = (null)}
2022-03-15 16:20:22.661857+0800 GCDios[17690:8322634] ------> Store: <NSThread: 0x280d84b00>{
number = 8, name = (null)}, Used Count: 2
2022-03-15 16:20:22.867473+0800 GCDios[17690:8322634] ------> SIGNAL: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d84b00>{
number = 8, name = (null)}
2022-03-15 16:20:22.867513+0800 GCDios[17690:8322635] ------> WAIT: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d86ac0>{
number = 7, name = (null)}
2022-03-15 16:20:22.867893+0800 GCDios[17690:8322635] ------> Store: <NSThread: 0x280d86ac0>{
number = 7, name = (null)}, Used Count: 1
2022-03-15 16:20:23.073678+0800 GCDios[17690:8322635] ------> SIGNAL: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d86ac0>{
number = 7, name = (null)}
2022-03-15 16:20:23.073685+0800 GCDios[17690:8322634] ------> WAIT: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d84b00>{
number = 8, name = (null)}
2022-03-15 16:20:23.074345+0800 GCDios[17690:8322634] ------> Store: <NSThread: 0x280d84b00>{
number = 8, name = (null)}, Used Count: 0
2022-03-15 16:20:23.278227+0800 GCDios[17690:8322634] ------> SIGNAL: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d84b00>{
number = 8, name = (null)}
2022-03-15 16:20:23.278236+0800 GCDios[17690:8322635] ------> WAIT: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d86ac0>{
number = 7, name = (null)}
2022-03-15 16:20:23.279220+0800 GCDios[17690:8322635] ------> Store: <NSThread: 0x280d86ac0>{
number = 7, name = (null)} has no car.
2022-03-15 16:20:23.280028+0800 GCDios[17690:8322634] ------> WAIT: <OS_dispatch_semaphore: 0x283bdf390> AT THREAD: <NSThread: 0x280d84b00>{
number = 8, name = (null)}
2022-03-15 16:20:23.280499+0800 GCDios[17690:8322634] ------> Store: <NSThread: 0x280d84b00>{
number = 8, name = (null)} has no car.
3.5 dispatch_barrier_async
有时候业务场景为先执行一组异步任务,接着再执行另外一组异步任务。这时候便可使用dispoatch_barrier_async进行栅栏分割操作。
- (void)barrierAysnc{
dispatch_queue_t concurrent_queue = dispatch_queue_create("com.barrier", DISPATCH_QUEUE_CONCURRENT);
__block NSInteger a = 0;
dispatch_async(concurrent_queue, ^{
[NSThread sleepForTimeInterval:2];
a++;
NSLog(@"------> 1 CURRENT THREAD: %@", [NSThread currentThread]);
});
dispatch_async(concurrent_queue, ^{
[NSThread sleepForTimeInterval:2];
a++;
NSLog(@"------> 2 CURRENT THREAD: %@", [NSThread currentThread]);
});
dispatch_async(concurrent_queue, ^{
[NSThread sleepForTimeInterval:5];
a++;
NSLog(@"------> 3 CURRENT THREAD: %@", [NSThread currentThread]);
});
dispatch_barrier_sync(concurrent_queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"------> value:%ld barrier CURRENT THREAD: %@", (long)a, [NSThread currentThread]);
});
dispatch_async(concurrent_queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"------> 4 CURRENT THREAD: %@", [NSThread currentThread]);
});
dispatch_async(concurrent_queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"------> 5 CURRENT THREAD: %@", [NSThread currentThread]);
});
}
在dispatch_barrier_async
执行之前,开启的子线层会对被操作资源(a)进行操作,dispatch_barrier_async
执行的时候,会利用资源进行进一步操作;此过程完毕,会执行第二组异步操作。
结果如下:
2022-03-21 15:28:08.445063+0800 GCDios[18226:9521504] ------> 1 CURRENT THREAD: <NSThread: 0x2838c0140>{
number = 5, name = (null)}
2022-03-21 15:28:08.445146+0800 GCDios[18226:9521499] ------> 2 CURRENT THREAD: <NSThread: 0x2838c0d40>{
number = 6, name = (null)}
2022-03-21 15:28:11.445473+0800 GCDios[18226:9521503] ------> 3 CURRENT THREAD: <NSThread: 0x2838c0b80>{
number = 7, name = (null)}
2022-03-21 15:28:13.447573+0800 GCDios[18226:9521489] ------> value:3 barrier CURRENT THREAD: <NSThread: 0x2838808c0>{
number = 1, name = main}
2022-03-21 15:28:15.452746+0800 GCDios[18226:9521503] ------> 4 CURRENT THREAD: <NSThread: 0x2838c0b80>{
number = 7, name = (null)}
2022-03-21 15:28:15.452800+0800 GCDios[18226:9521499] ------> 5 CURRENT THREAD: <NSThread: 0x2838c0d40>{
number = 6, name = (null)}
四:dispatch_semaphore浅谈
4.1 dispatch_semaphore_t
dispatch_semaphore_t
用来声明dispatch_semaphore函数,创建则使用dispatch_semaphore_create
函数,该函数依赖于结构体dispatch_semaphore_s,后者结构体声明如下:
struct dispatch_semaphore_s {
DISPATCH_STRUCT_HEADER(semaphore);
semaphore_t dsema_port; //等同于mach_port_t信号
long dsema_orig; //初始化的信号量值
long volatile dsema_value; //当前信号量值
union {
long volatile dsema_sent_ksignals;
long volatile dsema_group_waiters;
};
struct dispatch_continuation_s *volatile dsema_notify_head; //notify的链表头部
struct dispatch_continuation_s *volatile dsema_notify_tail; //notify的链表尾部
};
dispatch_semaphore_create函数内部申请地址的时候会用到dispatch_semaphore_s结构体,其dsma变量会根据dispatch_semaphore计算出合适的空间。dispatcH-semaphore_t的函数内部如下:
dispatch_semaphore_t dispatch_semaphore_create(long value) {
dispatch_semaphore_t dsema;
if (value < 0) {
//value值需大于或等于0
return NULL;
}
//申请dispatch_semaphore_t的内存
dsema = (dispatch_semaphore_t)_dispatch_alloc(DISPATCH_VTABLE(semaphore),
sizeof(struct dispatch_semaphore_s) -
sizeof(dsema->dsema_notify_head) -
sizeof(dsema->dsema_notify_tail));
//调用初始化函数
_dispatch_semaphore_init(value, dsema);
return dsema;
}
//初始化结构体信息
static void _dispatch_semaphore_init(long value, dispatch_object_t dou) {
dispatch_semaphore_t dsema = dou._dsema;
dsema->do_next = (dispatch_semaphore_t)DISPATCH_OBJECT_LISTLESS;
dsema->do_targetq = dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dsema->dsema_value = value; //设置信号量的当前value值
dsema->dsema_orig = value; //设置信号量的初始value值
}
从这里我们可以看到,当调用dispatch_semaphore_create()
函数时,若初始化值小于1则会返回空,这是我们需要注意的地方。而另一个需要注意的地方为dispatch_semaphore
销毁的时候。销毁时调用函数如下:
//释放信号量的函数
void _dispatch_semaphore_dispose(dispatch_object_t dou) {
dispatch_semaphore_t dsema = dou._dsema;
if (dsema->dsema_value < dsema->dsema_orig) {
//Warning:信号量还在使用的时候销毁会造成崩溃
DISPATCH_CLIENT_CRASH(
"Semaphore/group object deallocated while in use");
}
kern_return_t kr;
if (dsema->dsema_port) {
kr = semaphore_destroy(mach_task_self(), dsema->dsema_port);
DISPATCH_SEMAPHORE_VERIFY_KR(kr);
}
}
可以看到,若当前信号量值小于初始化值时会崩溃,因此一定要慎重注意到设置dispatch_semaphore的值在信号量尚在使用时不可设置其为nil或重新赋值。
4.2 dispatch_semaphore_wait
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout){
long value = dispatch_atomic_dec2o(dsema, dsema_value, acquire);
if (fastpath(value >= 0)) {
return 0;
}
return _dispatch_semaphore_wait_slow(dsema, timeout);
}
dispatch_semaphore_wait
先将信号量的dsema值原子性减一,并将新值赋给value。如果value大于等于0就立即返回,否则调用_dispatch_semaphore_wait_slow
函数,等待信号量唤醒或者timeout超时。_dispatch_semaphore_wait_slow
函数的实现较为复杂,这里不再展开赘述。
4.3 dispatch_semaphore_signal
long dispatch_semaphore_signal(dispatch_semaphore_t dsema) {
long value = dispatch_atomic_inc2o(dsema, dsema_value, release);
if (fastpath(value > 0)) {
return 0;
}
if (slowpath(value == LONG_MIN)) {
DISPATCH_CLIENT_CRASH("Unbalanced call to dispatch_semaphore_signal()");
}
return _dispatch_semaphore_signal_slow(dsema);
}
首先将dsema_value调用原子方法加1,如果大于零就立即返回0,否则进入_dispatch_semaphore_signal_slow
函数,该函数会调用semaphore_signal
函数唤醒在dispatch_semaphore_wait
中等待的线程。_dispatch_semaphore_signal_slow
同样不再赘述。
五:dispatch_group浅谈
dispatch_group
的创建实际上是依赖于dispatch_semaphore
,下面就常用函数进行浅谈。
5.1 dispatch_group_create
dispatch_group_t dispatch_group_create(void) {
return (dispatch_group_t)dispatch_semaphore_create(LONG_MAX);
}
可以看到,dispatch_group_create
就是创造了以LONG_MAX
为信号量的dispatch_semaphore,至于为什么信号量为LONG_MAX
,猜测可能是为了防止使用者过多地调用而崩溃特意将信号量调大。
5.2 dispatch_group_enter
void dispatch_group_enter(dispatch_group_t dg) {
dispatch_semaphore_t dsema = (dispatch_semaphore_t)dg;
(void)dispatch_semaphore_wait(dsema, DISPATCH_TIME_FOREVER);
}
可以看到,dispatch_group实际上就是以当前group为参数,发出wait信号量。
5.3 dispatch_group_leave
void dispatch_group_leave(dispatch_group_t dg) {
dispatch_semaphore_t dsema = (dispatch_semaphore_t)dg;
dispatch_atomic_release_barrier();
long value = dispatch_atomic_inc2o(dsema, dsema_value);
//dsema_value原子性加1
if (slowpath(value == LONG_MIN)) {
//内存溢出,由于dispatch_group_leave在dispatch_group_enter之前调用
DISPATCH_CLIENT_CRASH("Unbalanced call to dispatch_group_leave()");
}
if (slowpath(value == dsema->dsema_orig)) {
//表示所有任务已经完成,唤醒group
(void)_dispatch_group_wake(dsema);
}
}
可以看到 dispatch_group_leave
将 dispatch_group_t
转换成 dispatch_semaphore_t
后将 dsema_value 的值原子性加1。如果 value 为 LONG_MIN 则崩溃;如果 value 等于 dsema_orig 表示所有任务已完成,调用 _dispatch_group_wake
唤醒group(即唤醒notify函数)
边栏推荐
猜你喜欢
随机推荐
postgresql 生成随机日期,随机时间
Emotional crisis, my friend's online dating girlfriend wants to break up with him, ask me what to do
开放麒麟 openKylin 自动化开发者平台正式发布
canvas粒子变幻各种形状js特效
第二十二课,实例化(instancing)
spark过滤器
(selenium)Service geckodriver unexpectedly exited. Status code was: 64
js空气质量aqi雷达图分析
第七章
PyQt5快速开发与实战 9.4 Matplotlib在PyQt中的应用
Kotlin—基本语法(三)
(C语言)程序环境和预处理
&#x开头的是什么编码?
【Redis高手修炼之路】Jedis——Jedis的基本使用
【机器学习】用特征量重要度(feature importance)解释模型靠谱么?怎么才能算出更靠谱的重要度?
各位大佬,sqlserver 支持表名正则匹配吗
一些计时软件,生产力工具
第五章
Gradle系列——Groovy概述,基础使用(基于Groovy文档4.0.4)day2-1
富文本编辑器Tinymce