继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

iOS 开发之 GCD 不同场景使用

烙印99
关注TA
已关注
手记 390
粉丝 92
获赞 446

本文在iOS 开发值 GCD 基础 的基础上,继续总结了 GCD 的一些API 和在不同场景下的使用。

GCD 栅栏方法:dispatch_barrier_async

我们有时需要异步执行两组操作,而且第一组操作执行完之后,才能开始执行第二组操作。这样我们就需要一个相当于 栅栏 一样的一个方法将两组异步执行的操作组给分割起来,当然这里的操作组里可以包含一个或多个任务。这就需要用到dispatch_barrier_async方法在两个操作组间形成栅栏。

dispatch_barrier_async函数会等待前边追加到并发队列中的任务全部执行完毕之后,再将指定的任务追加到该异步队列中。然后在dispatch_barrier_async函数追加的任务执行完毕之后,异步队列才恢复为一般动作,接着追加任务到该异步队列并开始执行.

示意如图:https://img4.mukewang.com/5b8765960001e3f711860442.jpg

 

/**
 * 栅栏方法 dispatch_barrier_async
 */

- (void)barrier {

    // 1. 创建并发队列队列
    dispatch_queue_t queue = dispatch_queue_create("com.xiaoyouPrince", DISPATCH_QUEUE_CONCURRENT);

    // 2. 添加异步任务
    dispatch_async(queue, ^{

        // 追加任务1
        for(inti = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });

    dispatch_async(queue, ^{
        // 追加任务2
        for(inti = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });

    // 栅栏任务
    dispatch_barrier_async(queue, ^{
        // 追加任务 barrier
        for(inti = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
        }
    });


    dispatch_async(queue, ^{
        // 追加任务3
        for(inti = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });

    dispatch_async(queue, ^{
        // 追加任务4
        for(inti = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"4---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
}


------------------打印结果-----------------
1---{number = 4, name = (null)}
2---{number = 3, name = (null)}
1---{number = 4, name = (null)}
2---{number = 3, name = (null)}
barrier---{number = 4, name = (null)}
barrier---{number = 4, name = (null)}
4---{number = 3, name = (null)}
3---{number = 4, name = (null)}
4---{number = 3, name = (null)}
3---{number = 4, name = (null)}
------------------打印结果-----------------

在dispatch_barrier_async相关代码执行结果中可以看出:

在执行完栅栏前面的操作之后,才执行栅栏操作,最后再执行栅栏后边的操作

GCD 延时执行方法:dispatch_after

我们经常会遇到这样的需求:在指定时间(例如3秒)之后执行某个任务。可以用 GCD 的dispatch_after函数来实现。

需要注意的是:dispatch_after函数并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中。严格来说,这个时间并不是绝对准确的,但想要大致延迟执行任务,dispatch_after函数是很有效的。

/**
 * 延时执行方法 dispatch_after
 */

- (void)after {

    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"after---begin");

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0* NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

        // 2.0秒后异步追加任务代码到主队列,并开始执行
        NSLog(@"after---%@",[NSThread currentThread]);  // 打印当前线程
    });

}

输出结果:

2018-08-29 17:53:08.713784+0800 GCD-demo[20282:5080295] currentThread---{number = 1, name = main}

2018-08-29 17:53:08.713962+0800 GCD-demo[20282:5080295] after---begin

2018-08-29 17:53:10.714283+0800 GCD-demo[20282:5080295] after---{number = 1, name = main}

在dispatch_after相关代码执行结果中可以看出:在打印 after---begin 之后大约 2.0 秒的时间,打印了 after---{number = 1, name = main}

GCD 一次性代码(只执行一次):dispatch_once

我们在创建单例、或者有整个程序运行过程中只执行一次的代码时,我们就用到了 GCD 的 dispatch_once 函数。使用dispatch_once 函数能保证某段代码在程序运行过程中只被执行1次,并且即使在多线程的环境下,dispatch_once也可以保证线程安全。

/**
 * 一次性代码(只执行一次)dispatch_once
 */

- (void)once {

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 只执行1次的代码(这里面默认是线程安全的)
    });
}

GCD 快速迭代方法:dispatch_apply

通常我们会用 for 循环遍历,但是 GCD 给我们提供了快速迭代的函数dispatch_apply。dispatch_apply按照指定的次数将指定的任务追加到指定的队列中,并等待全部队列执行结束。

我们可以利用异步队列同时遍历。比如说遍历 0~5 这6个数字,for 循环的做法是每次取出一个元素,逐个遍历。dispatch_apply可以同时遍历多个数字。

/**
 * 快速迭代方法 dispatch_apply
 */

- (void)apply {

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    NSLog(@"apply---begin");

    dispatch_apply(6, queue, ^(size_t index) {
        NSLog(@"%zd---%@",index, [NSThread currentThread]);
    });
    
    NSLog(@"apply---end");
}

--------------------输出结果:-------------------

apply---begin

1---{number = 3, name = (null)}
0---{number = 1, name = main}
2---{number = 4, name = (null)}
3---{number = 5, name = (null)}
4---{number = 3, name = (null)}
5---{number = 1, name = main}

apply---end

--------------------输出结果:-------------------

从 dispatch_apply 相关代码执行结果中可以看出:

0~5 打印顺序不定,最后打印了 apply---end。

因为是在并发队列中异步队执行任务,所以各个任务的执行时间长短不定,最后结束顺序也不定。但是apply---end一定在最后执行。这是因为 dispatch_apply 函数会等待全部任务执行完毕。

GCD 的队列组:dispatch_group

有时候我们会有这样的需求:分别异步执行2个耗时任务,然后当2个耗时任务都执行完毕后再回到主线程执行任务。这时候我们可以用到 GCD 的队列组。

调用队列组的 dispatch_group_async 先把任务放到队列中,然后将队列放入队列组中。或者使用队列组的 dispatch_group_enter、dispatch_group_leave 组合 来实现dispatch_group_async。

调用队列组的 dispatch_group_notify 回到指定线程执行任务。或者使用 dispatch_group_wait回到当前线程继续向下执行(会阻塞当前线程)。

dispatch_group_notify

监听 group 中任务的完成状态,当所有的任务都执行完成后,追加任务到 group 中,并执行任务。

/**
 * 队列组 dispatch_group_notify
 */

- (void)groupNotify {

    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"group---begin");

    // 1. 创建 group
    dispatch_group_t group =  dispatch_group_create();
    
    // 2. 异步执行组内的全局队列任务
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        // 追加任务1
        for(inti = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        // 追加任务2
        for(inti = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });



    dispatch_group_notify(group, dispatch_get_main_queue(), ^{

        // 等前面的异步任务1、任务2都执行完毕后,回到主线程执行下边任务
        for(inti = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
        }
        NSLog(@"group---end");
    });
}

-------------------输出结果:-----------------
currentThread---{number = 1, name = main}
group---begin

 1---{number = 4, name = (null)}
 2---{number = 3, name = (null)}
 2---{number = 3, name = (null)}
 1---{number = 4, name = (null)}
 3---{number = 1, name = main}
 3---{number = 1, name = main}
 
group---end

从dispatch_group_notify相关代码运行输出结果可以看出:
当所有任务都执行完成之后,才执行 dispatch_group_notify block 中的任务。

dispatch_group_wait

暂停当前线程(阻塞当前线程),等待指定的 group 中的任务执行完成后,才会往下继续执行。

/**
 * 队列组 dispatch_group_wait
 */
- (void)groupWait {

    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"group---begin");

    // 1. 创建group
    dispatch_group_t group =  dispatch_group_create();

    // 2. 异步执行组中全局队列任务
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        // 追加任务1
        for(inti = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任务2
        for(inti = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });

    // 等待上面的任务全部完成后,会往下继续执行(会阻塞当前线程)
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    
    NSLog(@"group---end");
}

------------------输出结果:------------------

currentThread---{number = 1, name = main}
group---begin
2---{number = 4, name = (null)}
1---{number = 3, name = (null)}
2---{number = 4, name = (null)}
1---{number = 3, name = (null)}
group---end
------------------输出结果:------------------

从dispatch_group_wait相关代码运行输出结果可以看出:

当所有任务执行完成之后,才执行 dispatch_group_wait 之后的操作。但是,使用dispatch_group_wait 会阻塞当前线程。

dispatch_group_enter、dispatch_group_leave

dispatch_group_enter 标志着一个任务追加到 group,执行一次,相当于 group 中未执行完毕任务数+1

dispatch_group_leave 标志着一个任务离开了 group,执行一次,相当于 group 中未执行完毕任务数-1。

当 group 中未执行完毕任务数为0的时候,才会使dispatch_group_wait解除阻塞,以及执行追加到dispatch_group_notify中的任务。

/**
 * 队列组 dispatch_group_enter、dispatch_group_leave
 */
- (void)groupEnterAndLeave
{

    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"group---begin");

    // 1.创建 group
    dispatch_group_t group = dispatch_group_create();

    // 2. 获取全局并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    // 3. 进入 group 中执行
    dispatch_group_enter(group);

    // 3.1. 向队列中异步执行任务
    dispatch_async(queue, ^{

        // 追加任务1
        for(inti = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        }
        
        // 3.2. 任务执行之后,离开组【leave 和 enter 必须成对使用】
        dispatch_group_leave(group);
    });


    // 4. 再次进入组执行新的任务
    dispatch_group_enter(group);
    // 4.1. 向队列中异步执行任务
    dispatch_async(queue, ^{

        // 追加任务2
        for(inti = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        }
        
        // 4.2. 任务执行之后,离开组【leave 和 enter 必须成对使用】
        dispatch_group_leave(group);
    });


    // group 内部的任务都执行完之后通知执行下面代码
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{

        // 等前面的异步操作都执行完毕后,回到主线程.
        for(inti = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
        }
        NSLog(@"group---end");
    });
}


-------------------输出结果------------------
currentThread---{number = 1, name = main}
group---begin
1---{number = 4, name = (null)}
2---{number = 3, name = (null)}
1---{number = 4, name = (null)}
2---{number = 3, name = (null)}
3---{number = 1, name = main}
3---{number = 1, name = main}
group---end
-------------------输出结果------------------

从dispatch_group_enter、dispatch_group_leave相关代码运行结果中可以看出:当所有任务执行完成之后,才执行 dispatch_group_notify 中的任务。这里的dispatch_group_enter、dispatch_group_leave组合,其实等同于dispatch_group_async。

GCD 信号量:dispatch_semaphore

GCD 中的信号量是指 Dispatch Semaphore,是持有计数的信号。类似于过高速路收费站的栏杆。可以通过时,打开栏杆,不可以通过时,关闭栏杆。在 Dispatch Semaphore 中,使用计数来完成这个功能,计数为0时等待,不可通过。计数为1或大于1时,计数减1且不等待,可通过。Dispatch Semaphore 提供了三个函数。

dispatch_semaphore_create:创建一个Semaphore并初始化信号的总量

dispatch_semaphore_signal:发送一个信号,让信号总量加1

dispatch_semaphore_wait:可以使总信号量减1,当信号总量为0时就会一直等待(阻塞所在线程),否则就可以正常执行。

注意:信号量的使用前提是:想清楚你需要处理哪个线程等待(阻塞),又要哪个线程继续执行,然后使用信号量。

Dispatch Semaphore 在实际开发中主要用于:

保持线程同步,将异步执行任务转换为同步执行任务

保证线程安全,为线程加锁

Dispatch Semaphore 线程同步

我们在开发中,会遇到这样的需求:异步执行耗时任务,并使用异步执行的结果进行一些额外的操作。换句话说,相当于,将将异步执行任务转换为同步执行任务。比如说:AFNetworking 中 AFURLSessionManager.m 里面的 tasksForKeyPath: 方法。通过引入信号量的方式,等待异步执行任务结果,获取到 tasks,然后再返回该 tasks。

- (NSArray *)tasksForKeyPath:(NSString *)keyPath {

    __block NSArray *tasks = nil;

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {

        if([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {

            tasks = dataTasks;

        } else if([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {

            tasks = uploadTasks;

        } else if([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {

            tasks = downloadTasks;

        } else if([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {

            tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];

        }
        
        dispatch_semaphore_signal(semaphore);
    }];

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    
    return tasks;
}

下面,我们来利用 Dispatch Semaphore 实现线程同步,将异步执行任务转换为同步执行任务。

/**
 * semaphore 线程同步
 */

- (void)semaphoreSync {

    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程

    NSLog(@"semaphore---begin");

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    __block int number = 0;

    dispatch_async(queue, ^{

        // 追加任务1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
         
        number = 100;

        dispatch_semaphore_signal(semaphore);
    });

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    NSLog(@"semaphore---end,number = %zd",number);

}


----------------------输出结果-----------------
currentThread---{number = 1, name = main}
semaphore---begin
1---{number = 3, name = (null)}
semaphore---end, number = 100

从 Dispatch Semaphore 实现线程同步的代码可以看到:

semaphore---end 是在执行完number = 100; 之后才打印的。而且输出结果 number 为 100。这是因为异步执行不会做任何等待,可以继续执行任务。异步执行将任务1追加到队列之后,不做等待,接着执行dispatch_semaphore_wait方法。此时 semaphore == 0,当前线程进入等待状态。然后,异步任务1开始执行。任务1执行到dispatch_semaphore_signal之后,总信号量加1,此时 semaphore == 1,dispatch_semaphore_wait方法检测到总信号量为1,正在被阻塞的线程(主线程)恢复继续执行。最后打印semaphore---end,number = 100。这样就实现了线程同步,将异步执行任务转换为同步执行任务。

Dispatch Semaphore 线程安全和线程同步(为线程加锁)

线程安全:如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作(更改变量),一般都需要考虑线程同步,否则的话就可能影响线程安全。

线程同步:可理解为线程 A 和 线程 B 一块配合,A 执行到一定程度时要依靠线程 B 的某个结果,于是停下来,示意 B 运行;B 依言执行,再将结果给 A;A 再继续操作。

举个简单例子就是:两个人在一起聊天。两个人不能同时说话,避免听不清(操作冲突)。等一个人说完(一个线程结束操作),另一个再说(另一个线程再开始操作)。

下面,我们模拟火车票售卖的方式,实现 NSThread 线程安全和解决线程同步问题。

场景:总共有50张火车票,有两个售卖火车票的窗口,一个是北京火车票售卖窗口,另一个是上海火车票售卖窗口。两个窗口同时售卖火车票,卖完为止。

非线程安全(不使用 semaphore)

先来看看不考虑线程安全的代码:

/**
 * 非线程安全:不使用 semaphore
 * 初始化火车票数量、卖票窗口(非线程安全)、并开始卖票
 */
- (void)initTicketStatusNotSave {

    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"semaphore---begin");

    self.ticketSurplusCount = 50; // 初始化车票 50 张

    // queue1 代表北京火车票售卖窗口
    dispatch_queue_t queue1 = dispatch_queue_create("com.xiaoyouPrince1", DISPATCH_QUEUE_SERIAL);

    // queue2 代表上海火车票售卖窗口
    dispatch_queue_t queue2 = dispatch_queue_create("com.xiaoyouPrince2", DISPATCH_QUEUE_SERIAL);

    // 北京窗口异步执行卖票操作
    __weak typeof(self) weakSelf = self;
    dispatch_async(queue1, ^{
        [weakSelf saleTicketNotSafe];
    });

    // 上海窗口异步执行卖票操作
    dispatch_async(queue2, ^{
        [weakSelf saleTicketNotSafe];
    });
}



/**
 * 售卖火车票(非线程安全)
 */

- (void)saleTicketNotSafe {

    while(1) {
    
        if(self.ticketSurplusCount > 0) {  //如果还有票,继续售卖
            self.ticketSurplusCount--;

            NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
            
        } else{ //如果已卖完,关闭售票窗口

            NSLog(@"票已经售完");
            break;
        }
    }
}


----------------- 输出结果(部分打印) -------------------
currentThread <NSThread: 0x608000079280>{number = 1, name = main}
non_semaphore_begin
剩余票数 48,窗口:{number = 4, name = (null)}
剩余票数 49,窗口:{number = 3, name = (null)}
剩余票数 47,窗口:{number = 4, name = (null)}
剩余票数 46,窗口:{number = 3, name = (null)}
剩余票数 45,窗口:{number = 4, name = (null)}
剩余票数 44,窗口:{number = 3, name = (null)}
剩余票数 43,窗口:{number = 4, name = (null)}
剩余票数 42,窗口:{number = 3, name = (null)}
剩余票数 40,窗口:{number = 4, name = (null)}
剩余票数 41,窗口:{number = 3, name = (null)}
剩余票数 39,窗口:{number = 4, name = (null)}
剩余票数 0,窗口:{number = 3, name = (null)}
...\
票已经售完
票已经售完
----------------- 输出结果(部分打印) -------------------

可以看到在不考虑线程安全,不使用 semaphore 的情况下,得到票数是错乱的,这样显然不符合我们的需求,所以我们需要考虑线程安全问题。

线程安全(使用 semaphore 加锁)

考虑线程安全的代码:

/**

 * 线程安全:使用 semaphore 加锁
 * 初始化火车票数量、卖票窗口(线程安全)、并开始卖票
 */

- (void)initTicketStatusSave {

    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"semaphore---begin");
    
    // 有成员变量 dispatch_semaphore_t semaphreLock
    semaphreLock = dispatch_semaphore_create(1);
    
    self.ticketSurplusCount = 50;
    
    // queue1 代表北京火车票售卖窗口
    dispatch_queue_t queue1 = dispatch_queue_create("com.xiaoyouPrince1", DISPATCH_QUEUE_SERIAL);

    // queue2 代表上海火车票售卖窗口
    dispatch_queue_t queue2 = dispatch_queue_create("com.xiaoyouPrince2", DISPATCH_QUEUE_SERIAL);
    
    __weak typeof(self) weakSelf = self;
    dispatch_async(queue1, ^{
        [weakSelf saleTicketSafe];
    });

    dispatch_async(queue2, ^{
        [weakSelf saleTicketSafe];
    });
}

/**
 * 售卖火车票(线程安全)
 */
- (void)saleTicketSafe {

    while(1) {

        // 相当于加锁
        dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);

        if(self.ticketSurplusCount > 0) {  //如果还有票,继续售卖

            self.ticketSurplusCount--;

            NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);

            [NSThread sleepForTimeInterval:0.2];

        } else{ //如果已卖完,关闭售票窗口

            NSLog(@"车票已经售完");
            
            // 相当于解锁
            dispatch_semaphore_signal(semaphoreLock);
            break;
        }
        
        // 相当于解锁
        dispatch_semaphore_signal(semaphoreLock);
    }
}

----------------- 输出结果(部分打印) -------------------
currentThread <NSThread: 0x608000079280>{number = 1, name = main}
non_semaphore_begin
剩余票数 49,窗口:{number = 4, name = (null)}
剩余票数 48,窗口:{number = 3, name = (null)}
剩余票数 47,窗口:{number = 4, name = (null)}
剩余票数 46,窗口:{number = 3, name = (null)}
剩余票数 45,窗口:{number = 4, name = (null)}
....\
剩余票数 4,窗口:{number = 3, name = (null)}
剩余票数 3,窗口:{number = 4, name = (null)}
剩余票数 2,窗口:{number = 3, name = (null)}
剩余票数 1,窗口:{number = 4, name = (null)}
剩余票数 0,窗口:{number = 3, name = (null)}
票已经售完
----------------- 输出结果(部分打印) -------------------

可以看出,在考虑了线程安全的情况下,使用dispatch_semaphore 机制之后,得到的票数是正确的,没有出现混乱的情况。我们也就解决了多个线程同步的问题。

创建一个 semaphoreLock 对象。后续可能有多个线程同时访问被车票余量,当有线程访问到票余量时进行 dispatch_semaphore_wait 操作。使得 semaphoreLock 总信号量 减 1 (等于0 ,相当于加锁) ,并继续操作票余量,进行一次 self.ticketSurplusCount-- 操作。当票数量减少之后执行 dispatch_semaphore_signal 使得 semaphoreLock 总信号量 + 1 (等于1 ,相当于解锁)

此过程中如果有其他并发线程要访问 票余量。同样会先来到 dispatch_semaphore_wait 操作。此时 semaphoreLock 的总信号量为 0 ,直接阻塞线程。当上一个访问余量的线程操作完成之后执行 dispatch_semaphore_signal 操作解锁之后,就会继续本线程的访问。从而确保了票余量的线程安全。

资料:
最新官方文档

原文出处:https://www.cnblogs.com/xiaoyouPrince/p/9557773.html


打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP