iOS多线程:GCD常用的多并发方法

最近在梳理iOS线程知识点,把常用的写下来,一方面方便记忆,也有利于后面复习,关于GCD的线程队列的方法这里就不再细致讲述,除了阻塞的情况需要注意下,另一篇文章中有专门针对阻塞做了介绍,其他串行、并行、同步、异步组合使用都好理解;这里就介绍下根据项目业务情况,常会使用到的一些GCD的方法

一:dispatch_after

/**
 * 延时执行方法 dispatch_after
 */
- (void)dispatchAfter {
    NSLog(@"currentThread---%@",[NSThread currentThread]);
    NSLog(@"asyncMain---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]);
    });
}
2020-01-14 19:56:50.174397+0800 Practice[30060:2516484] currentThread---<NSThread: 0x60000106ad80>{number = 1, name = main}
2020-01-14 19:56:50.174535+0800 Practice[30060:2516484] asyncMain---begin
2020-01-14 19:56:52.174763+0800 Practice[30060:2516484] after---<NSThread: 0x60000106ad80>{number = 1, name = main}

这里需要说明的是,dispatch_after不是在2秒之后开始执行任务,而是在2秒之后才开始把任务添加到队列中 。

二、dispatch_apply

/**
 * 快速迭代方法 dispatch_apply
 */
- (void)dispatchApply {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    NSLog(@"apply---begin");
        
    NSTimeInterval start1 = [[NSDate date] timeIntervalSince1970];
    dispatch_apply(6, queue, ^(size_t index) {
        NSLog(@"%zd---%@",index, [NSThread currentThread]);
    });
    NSLog(@"apply---end");
}

输出日志:

2020-01-14 20:01:19.805576+0800 Practice[30117:2521131] apply---begin
2020-01-14 20:01:19.805779+0800 Practice[30117:2521131] 0---<NSThread: 0x600002f31280>{number = 1, name = main}
2020-01-14 20:01:19.805848+0800 Practice[30117:2521183] 1---<NSThread: 0x600002f13a40>{number = 5, name = (null)}
2020-01-14 20:01:19.805866+0800 Practice[30117:2521182] 3---<NSThread: 0x600002f4c5c0>{number = 4, name = (null)}
2020-01-14 20:01:19.805866+0800 Practice[30117:2521184] 2---<NSThread: 0x600002f4c580>{number = 3, name = (null)}
2020-01-14 20:01:19.805873+0800 Practice[30117:2521181] 4---<NSThread: 0x600002f5b900>{number = 6, name = (null)}
2020-01-14 20:01:19.805913+0800 Practice[30117:2521346] 5---<NSThread: 0x600002fa82c0>{number = 7, name = (null)}
2020-01-14 20:01:19.806006+0800 Practice[30117:2521131] apply---end

dispatch_apply会阻塞当前线程,直到遍历任务执行完,才会继续向下执行,遍历过程中会开启多个线程执行任务,这个方法在大的并发量中才会体现优势,上代码如下:

- (void)dispatchApply {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    NSLog(@"apply---begin");

    int flag = 150000;

    NSTimeInterval start1 = [[NSDate date] timeIntervalSince1970];
    dispatch_apply(flag, queue, ^(size_t index) {
        NSLog(@"%zd---%@",index, [NSThread currentThread]);
    });
    NSTimeInterval end1 = [[NSDate date] timeIntervalSince1970];
    NSLog(@"surplus1 = %f", end1 - start1);
    NSLog(@"apply---end");

    NSTimeInterval start2 = [[NSDate date] timeIntervalSince1970];
    for (int i = 0; i < flag; i ++) {
        NSLog(@"%d---%@",i, [NSThread currentThread]);
    }
    NSTimeInterval end2 = [[NSDate date] timeIntervalSince1970];
    NSLog(@"surplus2 = %f", end2 - start2);
}

 设置大并发量flag=150000,输出时间对比:

2020-01-14 20:06:08.890703+0800 Practice[30166:2524954] surplus1 = 19.610913
2020-01-14 20:06:35.516065+0800 Practice[30166:2524954] surplus2 = 26.625173

在大并发量下,两种方式耗时时间对比比较明显

三、dispatch_barrier_async:栅栏防范,实现让一部分异步操作先执行,执行完,后面的异步操作再执行;

代码展示如下:

- (void)barrierTest {
    dispatch_queue_t queue = dispatch_queue_create("net.cloudsky.testQueue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        // 任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        // 任务 2
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);
    });
    
    dispatch_barrier_async(queue, ^{
        // 任务 barrier
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"barrier---%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        // 任务 3
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"3---%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        // 任务 4
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"4---%@",[NSThread currentThread]);
    });
}

日志输出:

2020-01-14 20:08:36.381930+0800 Practice[30194:2528006] 1---<NSThread: 0x600003137400>{number = 5, name = (null)}
2020-01-14 20:08:36.381930+0800 Practice[30194:2528004] 2---<NSThread: 0x6000031a5180>{number = 6, name = (null)}
2020-01-14 20:08:38.385603+0800 Practice[30194:2528006] barrier---<NSThread: 0x600003137400>{number = 5, name = (null)}
2020-01-14 20:08:40.388811+0800 Practice[30194:2528006] 3---<NSThread: 0x600003137400>{number = 5, name = (null)}
2020-01-14 20:08:40.388812+0800 Practice[30194:2528004] 4---<NSThread: 0x6000031a5180>{number = 6, name = (null)}

可以看出,barrier会让1、2的任务先执行完,再执行3、4的任务

四、dispatch_group_t: 队列组,可将执行操作的多个任务添加到队列中,再将队列添加到队列组中,可添加多个队列,分组执行任务,执行完发出通知做后面的操作;总结下来,有三种实现方式能够达到这种效果,下面一一列出:

1dispatch_group_notify,利用这种方式在执行完一系列操作后通知主线程执行操作,示例代码如下:

- (void)groupNotify {
    NSLog(@"currentThread---%@",[NSThread currentThread]);
    NSLog(@"group---begin");
    
    dispatch_group_t group =  dispatch_group_create();
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 任务 2
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的异步任务 1、任务 2 都执行完毕后,回到主线程执行下边任务
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"3---%@",[NSThread currentThread]);

        NSLog(@"group---end");
    });
}

日志输出:

2020-01-14 20:10:24.327701+0800 Practice[30221:2529716] currentThread---<NSThread: 0x600002106140>{number = 1, name = main}
2020-01-14 20:10:24.327827+0800 Practice[30221:2529716] group---begin
2020-01-14 20:10:26.331691+0800 Practice[30221:2529783] 1---<NSThread: 0x60000213dc00>{number = 5, name = (null)}
2020-01-14 20:10:26.331737+0800 Practice[30221:2529788] 2---<NSThread: 0x600002182080>{number = 6, name = (null)}
2020-01-14 20:10:28.333223+0800 Practice[30221:2529716] 3---<NSThread: 0x600002106140>{number = 1, name = main}
2020-01-14 20:10:28.333571+0800 Practice[30221:2529716] group---end

可以看到,异步队列组中的任务全部执行完后,dispatch_group_notify接收到通知并做更新操作,如任务3的执行。

2、dispatch_group_wait

- (void)groupWait {
    NSLog(@"currentThread---%@",[NSThread currentThread]);
    NSLog(@"group---begin");
    
    dispatch_group_t group =  dispatch_group_create();
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 任务 2
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);
    });
    
    // 等待上面的任务全部完成后,会往下继续执行(会阻塞当前线程)
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    
    NSLog(@"group---end");
}

日志:

2020-01-14 20:12:52.973077+0800 Practice[30254:2532086] currentThread---<NSThread: 0x6000010dedc0>{number = 1, name = main}
2020-01-14 20:12:52.973219+0800 Practice[30254:2532086] group---begin
2020-01-14 20:12:54.978057+0800 Practice[30254:2532140] 1---<NSThread: 0x6000010b4580>{number = 4, name = (null)}
2020-01-14 20:12:54.978056+0800 Practice[30254:2532139] 2---<NSThread: 0x600001089cc0>{number = 5, name = (null)}
2020-01-14 20:12:54.978461+0800 Practice[30254:2532086] group---end

说明:dispatch_group_wait会阻塞当前线程,待异步队列组中的任务执行完后,才会放通向下继续执行,执行效果上跟前者类似。

3、dispatch_group_enter、dispatch_group_leave,通过这两个方法配合dispatch_async使用,也能达到队列组的效果;dispatch_group_enter、dispatch_group_leave有些类似引用计数的原理,进行+1,-1的操作,二者必须成对出现,当计数为0时,代表当前队列组中的任务已经执行完毕,可以继续向下执行;示例代码如下:

- (void)groupEnterAndLeave {
    NSLog(@"currentThread---%@",[NSThread currentThread]);
    NSLog(@"group---begin");
    
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        // 任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);

        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        // 任务 2
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);
        
        dispatch_group_leave(group);
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的异步操作都执行完毕后,回到主线程.
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"3---%@",[NSThread currentThread]);
    
        NSLog(@"group---end");
    });
}

日志:

2020-01-14 20:16:53.427547+0800 Practice[30298:2535216] currentThread---<NSThread: 0x600000f06e40>{number = 1, name = main}
2020-01-14 20:16:53.427682+0800 Practice[30298:2535216] group---begin
2020-01-14 20:16:55.430099+0800 Practice[30298:2535269] 1---<NSThread: 0x600000f69440>{number = 3, name = (null)}
2020-01-14 20:16:55.430109+0800 Practice[30298:2535270] 2---<NSThread: 0x600000f62740>{number = 6, name = (null)}
2020-01-14 20:16:57.431377+0800 Practice[30298:2535216] 3---<NSThread: 0x600000f06e40>{number = 1, name = main}
2020-01-14 20:16:57.431571+0800 Practice[30298:2535216] group---end

 总结:实现效果上,这三种方式都能达到执行多个异步任务,最终返回刷新的效果,可根据项目实际情况选择使用。

五、关于信号量semaphore,个人理解信号量是用来控制在相同时间对同一资源的访问,有点类似加锁;

信号量常用方法如下:

dispatch_semaphore_create(1):创建信号量,并使信号量的初始值为1,即当前只能允许一个访问;当然根据初始值传入的不同,可控制当前允许访问的个数;

dispatch_semaphore_signal:使当前允许访问的个数加1;

dispatch_semaphore_wait:使当前允许访问的个数减1,当允许访问的个数为0时,后续的访问操作将无法执行,即资源不再开放访问;

常使用的场景如下:

示例1:阻塞同一线程;

- (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);
}

输出日志:

020-01-14 20:19:23.123990+0800 Practice[30325:2537438] currentThread---<NSThread: 0x600002a52e40>{number = 1, name = main}
2020-01-14 20:19:23.124126+0800 Practice[30325:2537438] semaphore---begin
2020-01-14 20:19:25.127939+0800 Practice[30325:2537509] 1---<NSThread: 0x600002a39180>{number = 4, name = (null)}
2020-01-14 20:19:25.128293+0800 Practice[30325:2537438] semaphore---end,number = 100

说明:由于创建信号量时,初始值为0,程序不会等异步任务dispatch_async执行完,而是继续向下执行,当主线程执行到dispatch_semaphore_wait时被阻塞,不再向下执行,当执行完dispatch_async中的任务时,dispatch_semaphore_signal发出信号,使信号量的值+1,这时主线程会被放通不再被阻塞,继续向下执行并打印输出结果;

示例2:多并发情况下控制对同一资源的访问,保证数据的实时准确性,这点在票务系统上体现的比较明显,示例代码如下:

第一种,线程不安全的情况:

- (void)buyTicketNotSafe {
    NSLog(@"currentThread---%@",[NSThread currentThread]);
    NSLog(@"semaphore---begin");
        
    self.totalTicketsCount = 50;
    
    // queue1 代表城市1火车票售卖窗口
    dispatch_queue_t queue1 = dispatch_queue_create("net.cloudsky.testQueue1", DISPATCH_QUEUE_SERIAL);
    // queue2 代表城市2火车票售卖窗口
    dispatch_queue_t queue2 = dispatch_queue_create("net.cloudsky.testQueue2", DISPATCH_QUEUE_SERIAL);
    
    __weak typeof(self) weakSelf = self;
    dispatch_async(queue1, ^{
        [weakSelf saleTicketNotSafe];
    });
    
    dispatch_async(queue2, ^{
        [weakSelf saleTicketNotSafe];
    });
}

/**
 * 售卖火车票(线程不安全)
 */
- (void)saleTicketNotSafe {
    while (1) {
        if (self.totalTicketsCount > 0) {  // 如果还有票,继续售卖
            self.totalTicketsCount--;
            NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%d 窗口:%@", self.totalTicketsCount, [NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
        } else { // 如果已卖完,关闭售票窗口
            NSLog(@"所有火车票均已售完");
            break;
        }
    }
}

输出日志:

2020-01-14 20:24:59.041765+0800 Practice[30386:2542921] currentThread---<NSThread: 0x6000020c6e40>{number = 1, name = main}
2020-01-14 20:24:59.041896+0800 Practice[30386:2542921] semaphore---begin
2020-01-14 20:24:59.042142+0800 Practice[30386:2542971] 剩余票数:49 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:24:59.042158+0800 Practice[30386:2542970] 剩余票数:48 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:24:59.245586+0800 Practice[30386:2542970] 剩余票数:46 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:24:59.245609+0800 Practice[30386:2542971] 剩余票数:47 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:24:59.447427+0800 Practice[30386:2542971] 剩余票数:44 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:24:59.447435+0800 Practice[30386:2542970] 剩余票数:45 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:24:59.650666+0800 Practice[30386:2542970] 剩余票数:43 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:24:59.650677+0800 Practice[30386:2542971] 剩余票数:42 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:24:59.854681+0800 Practice[30386:2542971] 剩余票数:40 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:24:59.854697+0800 Practice[30386:2542970] 剩余票数:41 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:00.057841+0800 Practice[30386:2542970] 剩余票数:39 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:00.057841+0800 Practice[30386:2542971] 剩余票数:38 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:00.263159+0800 Practice[30386:2542970] 剩余票数:36 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:00.263159+0800 Practice[30386:2542971] 剩余票数:37 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:00.465090+0800 Practice[30386:2542970] 剩余票数:35 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:00.465110+0800 Practice[30386:2542971] 剩余票数:35 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:00.666917+0800 Practice[30386:2542970] 剩余票数:33 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:00.666914+0800 Practice[30386:2542971] 剩余票数:34 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:00.871344+0800 Practice[30386:2542970] 剩余票数:31 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:00.871344+0800 Practice[30386:2542971] 剩余票数:32 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:01.075880+0800 Practice[30386:2542970] 剩余票数:30 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:01.075880+0800 Practice[30386:2542971] 剩余票数:29 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:01.280835+0800 Practice[30386:2542971] 剩余票数:27 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:01.280843+0800 Practice[30386:2542970] 剩余票数:28 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:01.481270+0800 Practice[30386:2542970] 剩余票数:26 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:01.481270+0800 Practice[30386:2542971] 剩余票数:26 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:01.683215+0800 Practice[30386:2542971] 剩余票数:24 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:01.683215+0800 Practice[30386:2542970] 剩余票数:25 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:01.885917+0800 Practice[30386:2542971] 剩余票数:23 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:01.885924+0800 Practice[30386:2542970] 剩余票数:22 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:02.087981+0800 Practice[30386:2542971] 剩余票数:21 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:02.087987+0800 Practice[30386:2542970] 剩余票数:20 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:02.288467+0800 Practice[30386:2542970] 剩余票数:19 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:02.288471+0800 Practice[30386:2542971] 剩余票数:18 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:02.491659+0800 Practice[30386:2542970] 剩余票数:17 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:02.491670+0800 Practice[30386:2542971] 剩余票数:16 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:02.697167+0800 Practice[30386:2542971] 剩余票数:14 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:02.697167+0800 Practice[30386:2542970] 剩余票数:15 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:02.898063+0800 Practice[30386:2542971] 剩余票数:13 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:02.898063+0800 Practice[30386:2542970] 剩余票数:12 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:03.101597+0800 Practice[30386:2542970] 剩余票数:11 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:03.101614+0800 Practice[30386:2542971] 剩余票数:10 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:03.304368+0800 Practice[30386:2542970] 剩余票数:8 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:03.304368+0800 Practice[30386:2542971] 剩余票数:9 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:03.505029+0800 Practice[30386:2542971] 剩余票数:7 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:03.505035+0800 Practice[30386:2542970] 剩余票数:6 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:03.705613+0800 Practice[30386:2542970] 剩余票数:5 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:03.705630+0800 Practice[30386:2542971] 剩余票数:4 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:03.905898+0800 Practice[30386:2542971] 剩余票数:3 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:03.905908+0800 Practice[30386:2542970] 剩余票数:2 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:04.108431+0800 Practice[30386:2542971] 剩余票数:0 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:04.108429+0800 Practice[30386:2542970] 剩余票数:1 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:04.310634+0800 Practice[30386:2542970] 所有火车票均已售完
2020-01-14 20:25:04.310650+0800 Practice[30386:2542971] 所有火车票均已售完

可以看到,当多个线程访问同一资源,当资源发生改变时,数据会不准确。

第二种:线程安全:

/**
 * 线程安全:使用 semaphore 加锁
 * 初始化火车票数量、卖票窗口(线程安全)、并开始卖票
 */
- (void)buyTicketSafe {
    NSLog(@"currentThread---%@",[NSThread currentThread]);
    NSLog(@"semaphore---begin");
    
    semaphoreLock = dispatch_semaphore_create(1);
    
    self.totalTicketsCount = 50;
    
    // queue1 代表城市1火车票售卖窗口
    dispatch_queue_t queue1 = dispatch_queue_create("net.cloudsky.testQueue1", DISPATCH_QUEUE_SERIAL);
    // queue2 代表城市2火车票售卖窗口
    dispatch_queue_t queue2 = dispatch_queue_create("net.cloudsky.testQueue2", 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.totalTicketsCount > 0) {  // 如果还有票,继续售卖
            self.totalTicketsCount--;
            NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%d 窗口:%@", self.totalTicketsCount, [NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
        } else { // 如果已卖完,关闭售票窗口
            NSLog(@"所有火车票均已售完");
            
            // 相当于解锁
            dispatch_semaphore_signal(semaphoreLock);
            break;
        }
        
        // 相当于解锁
        dispatch_semaphore_signal(semaphoreLock);
    }
}

日志:

2020-01-14 20:28:11.689914+0800 Practice[30418:2546067] currentThread---<NSThread: 0x600000736080>{number = 1, name = main}
2020-01-14 20:28:11.690040+0800 Practice[30418:2546067] semaphore---begin
2020-01-14 20:28:11.690368+0800 Practice[30418:2546177] 剩余票数:49 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:11.890740+0800 Practice[30418:2546560] 剩余票数:48 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:12.094728+0800 Practice[30418:2546177] 剩余票数:47 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:12.297269+0800 Practice[30418:2546560] 剩余票数:46 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:12.502602+0800 Practice[30418:2546177] 剩余票数:45 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:12.706331+0800 Practice[30418:2546560] 剩余票数:44 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:12.906836+0800 Practice[30418:2546177] 剩余票数:43 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:13.107579+0800 Practice[30418:2546560] 剩余票数:42 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:13.310564+0800 Practice[30418:2546177] 剩余票数:41 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:13.513896+0800 Practice[30418:2546560] 剩余票数:40 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:13.714300+0800 Practice[30418:2546177] 剩余票数:39 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:13.914735+0800 Practice[30418:2546560] 剩余票数:38 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:14.119221+0800 Practice[30418:2546177] 剩余票数:37 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:14.322066+0800 Practice[30418:2546560] 剩余票数:36 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:14.524925+0800 Practice[30418:2546177] 剩余票数:35 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:14.728081+0800 Practice[30418:2546560] 剩余票数:34 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:14.931475+0800 Practice[30418:2546177] 剩余票数:33 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:15.133113+0800 Practice[30418:2546560] 剩余票数:32 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:15.337582+0800 Practice[30418:2546177] 剩余票数:31 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:15.539476+0800 Practice[30418:2546560] 剩余票数:30 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:15.742918+0800 Practice[30418:2546177] 剩余票数:29 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:15.947115+0800 Practice[30418:2546560] 剩余票数:28 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:16.151334+0800 Practice[30418:2546177] 剩余票数:27 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:16.356116+0800 Practice[30418:2546560] 剩余票数:26 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:16.558461+0800 Practice[30418:2546177] 剩余票数:25 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:16.761620+0800 Practice[30418:2546560] 剩余票数:24 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:16.963740+0800 Practice[30418:2546177] 剩余票数:23 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:17.166348+0800 Practice[30418:2546560] 剩余票数:22 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:17.367036+0800 Practice[30418:2546177] 剩余票数:21 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:17.572432+0800 Practice[30418:2546560] 剩余票数:20 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:17.777916+0800 Practice[30418:2546177] 剩余票数:19 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:17.980030+0800 Practice[30418:2546560] 剩余票数:18 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:18.185497+0800 Practice[30418:2546177] 剩余票数:17 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:18.388987+0800 Practice[30418:2546560] 剩余票数:16 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:18.589401+0800 Practice[30418:2546177] 剩余票数:15 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:18.789993+0800 Practice[30418:2546560] 剩余票数:14 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:18.994816+0800 Practice[30418:2546177] 剩余票数:13 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:19.200268+0800 Practice[30418:2546560] 剩余票数:12 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:19.401382+0800 Practice[30418:2546177] 剩余票数:11 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:19.605777+0800 Practice[30418:2546560] 剩余票数:10 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:19.811006+0800 Practice[30418:2546177] 剩余票数:9 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:20.016176+0800 Practice[30418:2546560] 剩余票数:8 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:20.217251+0800 Practice[30418:2546177] 剩余票数:7 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:20.422595+0800 Practice[30418:2546560] 剩余票数:6 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:20.628031+0800 Practice[30418:2546177] 剩余票数:5 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:20.833101+0800 Practice[30418:2546560] 剩余票数:4 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:21.034921+0800 Practice[30418:2546177] 剩余票数:3 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:21.237287+0800 Practice[30418:2546560] 剩余票数:2 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:21.439123+0800 Practice[30418:2546177] 剩余票数:1 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:21.640437+0800 Practice[30418:2546560] 剩余票数:0 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:21.841116+0800 Practice[30418:2546177] 所有火车票均已售完
2020-01-14 20:28:21.841347+0800 Practice[30418:2546560] 所有火车票均已售完

说明:多并发情况下通过dispatch_semaphore_signal,dispatch_semaphore_wait控制信号量的信号数,实现加锁的功能,保证同一时间资源只被一个操作访问和改变,保证当前显示的票数是真实准确的,当然线程锁也能达到同样的效果;如果多并发情况下不对资源访问加以控制,同一时间多个线程访问改变同一资源,就会带来资源数据的不准确性。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值