本文介绍: 一:基础名称GCD的创建依赖于任务队列两个概念任务就是block执行操作block调用的某个方法任务有两种方式,一为同步执行,二为异步执行二者区别在于是否具备开启线程能力,执行的任务队列中执行的方式(顺序)。同步执行的特点同步添加任务指定队列中,在添加任务执行结束之前,会一直等待,直到队列里面任务完成之后再继续执行。只能在当前线程中执行任务,不具备开启线程的能力。异步执行的特点:异步添加任务指定队列中,它不会做任何等待可以继续执行任务。

一:基础名称

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_leavedispatch_group_t 转换成 dispatch_semaphore_t 后将 dsema_value 的值原子性加1。如果 value 为 LONG_MIN 则崩溃;如果 value 等于 dsema_orig 表示所有任务已完成,调用 _dispatch_group_wake 唤醒group(即唤醒notify函数)

原文地址:https://blog.csdn.net/kicinio/article/details/123497594

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_35580.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注