一:基础名称

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: {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: {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: {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: {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: {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: {number = 1, name = main}

2022-03-15 16:55:45.889676+0800 GCDios[17704:8329687] ------> Before wait Thread: {number = 1, name = main} AND PARA A:20

2022-03-15 16:55:47.894895+0800 GCDios[17704:8329702] ------> Thread: {number = 6, name = (null)}

2022-03-15 16:55:47.895247+0800 GCDios[17704:8329687] ------> After wait Thread: {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: AT THREAD: {number = 7, name = (null)}

2022-03-15 16:20:19.170332+0800 GCDios[17690:8322635] ------> WAIT: AT THREAD: {number = 7, name = (null)}

2022-03-15 16:20:19.170381+0800 GCDios[17690:8322635] ------> Store: {number = 7, name = (null)}, Used Count: 19

2022-03-15 16:20:19.170467+0800 GCDios[17690:8322634] ------> FIRST: AT THREAD: {number = 8, name = (null)}

2022-03-15 16:20:19.371599+0800 GCDios[17690:8322635] ------> SIGNAL: AT THREAD: {number = 7, name = (null)}

2022-03-15 16:20:19.371779+0800 GCDios[17690:8322634] ------> WAIT: AT THREAD: {number = 8, name = (null)}

2022-03-15 16:20:19.371850+0800 GCDios[17690:8322634] ------> Store: {number = 8, name = (null)}, Used Count: 18

2022-03-15 16:20:19.572499+0800 GCDios[17690:8322635] ------> WAIT: AT THREAD: {number = 7, name = (null)}

2022-03-15 16:20:19.572502+0800 GCDios[17690:8322634] ------> SIGNAL: AT THREAD: {number = 8, name = (null)}

2022-03-15 16:20:19.572929+0800 GCDios[17690:8322635] ------> Store: {number = 7, name = (null)}, Used Count: 17

2022-03-15 16:20:19.778504+0800 GCDios[17690:8322635] ------> SIGNAL: AT THREAD: {number = 7, name = (null)}

2022-03-15 16:20:19.778827+0800 GCDios[17690:8322634] ------> WAIT: AT THREAD: {number = 8, name = (null)}

2022-03-15 16:20:19.779001+0800 GCDios[17690:8322634] ------> Store: {number = 8, name = (null)}, Used Count: 16

2022-03-15 16:20:19.984579+0800 GCDios[17690:8322634] ------> SIGNAL: AT THREAD: {number = 8, name = (null)}

2022-03-15 16:20:19.984587+0800 GCDios[17690:8322635] ------> WAIT: AT THREAD: {number = 7, name = (null)}

2022-03-15 16:20:19.985258+0800 GCDios[17690:8322635] ------> Store: {number = 7, name = (null)}, Used Count: 15

2022-03-15 16:20:20.191370+0800 GCDios[17690:8322635] ------> SIGNAL: AT THREAD: {number = 7, name = (null)}

2022-03-15 16:20:20.191516+0800 GCDios[17690:8322634] ------> WAIT: AT THREAD: {number = 8, name = (null)}

2022-03-15 16:20:20.192384+0800 GCDios[17690:8322634] ------> Store: {number = 8, name = (null)}, Used Count: 14

2022-03-15 16:20:20.393759+0800 GCDios[17690:8322634] ------> SIGNAL: AT THREAD: {number = 8, name = (null)}

2022-03-15 16:20:20.393768+0800 GCDios[17690:8322635] ------> WAIT: AT THREAD: {number = 7, name = (null)}

2022-03-15 16:20:20.394415+0800 GCDios[17690:8322635] ------> Store: {number = 7, name = (null)}, Used Count: 13

2022-03-15 16:20:20.597513+0800 GCDios[17690:8322635] ------> SIGNAL: AT THREAD: {number = 7, name = (null)}

2022-03-15 16:20:20.597750+0800 GCDios[17690:8322634] ------> WAIT: AT THREAD: {number = 8, name = (null)}

2022-03-15 16:20:20.598420+0800 GCDios[17690:8322634] ------> Store: {number = 8, name = (null)}, Used Count: 12

2022-03-15 16:20:20.804358+0800 GCDios[17690:8322634] ------> SIGNAL: AT THREAD: {number = 8, name = (null)}

2022-03-15 16:20:20.804525+0800 GCDios[17690:8322635] ------> WAIT: AT THREAD: {number = 7, name = (null)}

2022-03-15 16:20:20.805148+0800 GCDios[17690:8322635] ------> Store: {number = 7, name = (null)}, Used Count: 11

2022-03-15 16:20:21.011005+0800 GCDios[17690:8322635] ------> SIGNAL: AT THREAD: {number = 7, name = (null)}

2022-03-15 16:20:21.011204+0800 GCDios[17690:8322634] ------> WAIT: AT THREAD: {number = 8, name = (null)}

2022-03-15 16:20:21.011824+0800 GCDios[17690:8322634] ------> Store: {number = 8, name = (null)}, Used Count: 10

2022-03-15 16:20:21.217719+0800 GCDios[17690:8322634] ------> SIGNAL: AT THREAD: {number = 8, name = (null)}

2022-03-15 16:20:21.218465+0800 GCDios[17690:8322635] ------> WAIT: AT THREAD: {number = 7, name = (null)}

2022-03-15 16:20:21.218938+0800 GCDios[17690:8322635] ------> Store: {number = 7, name = (null)}, Used Count: 9

2022-03-15 16:20:21.424744+0800 GCDios[17690:8322635] ------> SIGNAL: AT THREAD: {number = 7, name = (null)}

2022-03-15 16:20:21.424779+0800 GCDios[17690:8322634] ------> WAIT: AT THREAD: {number = 8, name = (null)}

2022-03-15 16:20:21.425369+0800 GCDios[17690:8322634] ------> Store: {number = 8, name = (null)}, Used Count: 8

2022-03-15 16:20:21.631025+0800 GCDios[17690:8322634] ------> SIGNAL: AT THREAD: {number = 8, name = (null)}

2022-03-15 16:20:21.631465+0800 GCDios[17690:8322635] ------> WAIT: AT THREAD: {number = 7, name = (null)}

2022-03-15 16:20:21.631723+0800 GCDios[17690:8322635] ------> Store: {number = 7, name = (null)}, Used Count: 7

2022-03-15 16:20:21.834804+0800 GCDios[17690:8322635] ------> SIGNAL: AT THREAD: {number = 7, name = (null)}

2022-03-15 16:20:21.834888+0800 GCDios[17690:8322634] ------> WAIT: AT THREAD: {number = 8, name = (null)}

2022-03-15 16:20:21.835452+0800 GCDios[17690:8322634] ------> Store: {number = 8, name = (null)}, Used Count: 6

2022-03-15 16:20:22.041551+0800 GCDios[17690:8322634] ------> SIGNAL: AT THREAD: {number = 8, name = (null)}

2022-03-15 16:20:22.042263+0800 GCDios[17690:8322635] ------> WAIT: AT THREAD: {number = 7, name = (null)}

2022-03-15 16:20:22.042735+0800 GCDios[17690:8322635] ------> Store: {number = 7, name = (null)}, Used Count: 5

2022-03-15 16:20:22.248610+0800 GCDios[17690:8322635] ------> SIGNAL: AT THREAD: {number = 7, name = (null)}

2022-03-15 16:20:22.248646+0800 GCDios[17690:8322634] ------> WAIT: AT THREAD: {number = 8, name = (null)}

2022-03-15 16:20:22.249263+0800 GCDios[17690:8322634] ------> Store: {number = 8, name = (null)}, Used Count: 4

2022-03-15 16:20:22.455297+0800 GCDios[17690:8322634] ------> SIGNAL: AT THREAD: {number = 8, name = (null)}

2022-03-15 16:20:22.455332+0800 GCDios[17690:8322635] ------> WAIT: AT THREAD: {number = 7, name = (null)}

2022-03-15 16:20:22.455917+0800 GCDios[17690:8322635] ------> Store: {number = 7, name = (null)}, Used Count: 3

2022-03-15 16:20:22.661461+0800 GCDios[17690:8322634] ------> WAIT: AT THREAD: {number = 8, name = (null)}

2022-03-15 16:20:22.661466+0800 GCDios[17690:8322635] ------> SIGNAL: AT THREAD: {number = 7, name = (null)}

2022-03-15 16:20:22.661857+0800 GCDios[17690:8322634] ------> Store: {number = 8, name = (null)}, Used Count: 2

2022-03-15 16:20:22.867473+0800 GCDios[17690:8322634] ------> SIGNAL: AT THREAD: {number = 8, name = (null)}

2022-03-15 16:20:22.867513+0800 GCDios[17690:8322635] ------> WAIT: AT THREAD: {number = 7, name = (null)}

2022-03-15 16:20:22.867893+0800 GCDios[17690:8322635] ------> Store: {number = 7, name = (null)}, Used Count: 1

2022-03-15 16:20:23.073678+0800 GCDios[17690:8322635] ------> SIGNAL: AT THREAD: {number = 7, name = (null)}

2022-03-15 16:20:23.073685+0800 GCDios[17690:8322634] ------> WAIT: AT THREAD: {number = 8, name = (null)}

2022-03-15 16:20:23.074345+0800 GCDios[17690:8322634] ------> Store: {number = 8, name = (null)}, Used Count: 0

2022-03-15 16:20:23.278227+0800 GCDios[17690:8322634] ------> SIGNAL: AT THREAD: {number = 8, name = (null)}

2022-03-15 16:20:23.278236+0800 GCDios[17690:8322635] ------> WAIT: AT THREAD: {number = 7, name = (null)}

2022-03-15 16:20:23.279220+0800 GCDios[17690:8322635] ------> Store: {number = 7, name = (null)} has no car.

2022-03-15 16:20:23.280028+0800 GCDios[17690:8322634] ------> WAIT: AT THREAD: {number = 8, name = (null)}

2022-03-15 16:20:23.280499+0800 GCDios[17690:8322634] ------> Store: {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: {number = 5, name = (null)}

2022-03-21 15:28:08.445146+0800 GCDios[18226:9521499] ------> 2 CURRENT THREAD: {number = 6, name = (null)}

2022-03-21 15:28:11.445473+0800 GCDios[18226:9521503] ------> 3 CURRENT THREAD: {number = 7, name = (null)}

2022-03-21 15:28:13.447573+0800 GCDios[18226:9521489] ------> value:3 barrier CURRENT THREAD: {number = 1, name = main}

2022-03-21 15:28:15.452746+0800 GCDios[18226:9521503] ------> 4 CURRENT THREAD: {number = 7, name = (null)}

2022-03-21 15:28:15.452800+0800 GCDios[18226:9521499] ------> 5 CURRENT THREAD: {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函数)

参考文章

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: