GCD总结篇

2020-03-20  本文已影响0人  收纳箱

GCD总结篇

1.GCD的优势

2. GCD任务和队列

先弄清2个最核心的概念:任务和队列

  1. 任务

    • 需要执行的代码逻辑。

    • GCD中放在block中。

    • 执行任务有两种方式:同步执行和异步执行。两者的区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。

      • 同步执行(sync):

        同步添加任务到指定队列中,阻塞当前线程任务后面的代码,直到任务执行完再执行。

      • 异步执行(async):

        异步添加任务到指定队列中,不阻塞当前线程任务后面的代码,且具备开启新线程的能力,在新的线程中执行任务。

  2. 队列(Dispatch Queue)

    • 存放任务的队列,任务执行的等待队列。

3. GCD使用步骤

3.1 队列的创建/获取

可以使用dispatch_queue_create来创建队列,需要传入两个参数:

//串行队列
dispatch_queue_t queue = dispatch_queue_create("com.company.demoQueue", DISPATCH_QUEUE_SERIAL);
//并发队列
dispatch_queue_t queue = dispatch_queue_create("com.company.demoQueue", DISPATCH_QUEUE_CONCURRENT);

对于串行队列,我们有一个最有名的特殊队列:主队列(Main Dispatch Queue)

//主队列
dispatch_queue_t queue = dispatch_get_main_queue();

相应的,GCD也默认提供了全局并发队列(Global Dispatch Queue)。使用dispatch_get_global_queue获取队列,需要传入两个参数:

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

3.2任务的创建方法

//同步任务
dispatch_sync(queue, ^{

});
//异步任务
dispatch_async(queue, ^{

􏲟􏲞􏱄􏱅􏱇􏱈􏲀􏲁});

我们有2种队列和2种任务创建方法,就有4种不同的组合方式:

实际上还有2种特殊的队列:全局并发队列和主队列。全局并发队列可以作为普通并发队列来使用。但主队列比较特殊,所以我们又多了两种组合方式。

下面会分别介绍这6种组合的使用。

4. GCD的基本使用

4.1 同步执行 + 并发队列

- (void)executeTasks
{
    NSLog(@"currentThread===%@", [NSThread currentThread]);
    NSLog(@"executeTasks === begin");
    
    dispatch_queue_t queue = dispatch_queue_create("com.company.demoQueue", DISPATCH_QUEUE_CONCURRENT);
    
    void(^block)(int) = ^(int idx){
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2.0]; //模拟耗时操作
            NSLog(@"%d === %@", idx, [NSThread currentThread]);
        }
    };
    
    dispatch_sync(queue, ^{
        block(1);
    });
    
    dispatch_sync(queue, ^{
        block(2);
    });
    
    dispatch_sync(queue, ^{
        block(3);
    });
    
    NSLog(@"executeTasks === end");
}

//输出
2020-03-19 14:22:26.574684+0800 Demo[4538:68451] currentThread===<NSThread: 0x600002b0e0c0>{number = 1, name = main}
2020-03-19 14:22:26.574839+0800 Demo[4538:68451] executeTasks === begin
2020-03-19 14:22:28.575302+0800 Demo[4538:68451] 1 === <NSThread: 0x600002b0e0c0>{number = 1, name = main}
2020-03-19 14:22:30.576971+0800 Demo[4538:68451] 1 === <NSThread: 0x600002b0e0c0>{number = 1, name = main}
2020-03-19 14:22:32.577451+0800 Demo[4538:68451] 2 === <NSThread: 0x600002b0e0c0>{number = 1, name = main}
2020-03-19 14:22:34.578525+0800 Demo[4538:68451] 2 === <NSThread: 0x600002b0e0c0>{number = 1, name = main}
2020-03-19 14:22:36.579760+0800 Demo[4538:68451] 3 === <NSThread: 0x600002b0e0c0>{number = 1, name = main}
2020-03-19 14:22:38.580209+0800 Demo[4538:68451] 3 === <NSThread: 0x600002b0e0c0>{number = 1, name = main}
2020-03-19 14:22:38.580381+0800 Demo[4538:68451] executeTasks === end

结果可以看到:

4.2 异步执行 + 并发队列

- (void)executeTasks
{
    NSLog(@"currentThread===%@", [NSThread currentThread]);
    NSLog(@"executeTasks === begin");
    
    dispatch_queue_t queue = dispatch_queue_create("com.company.demoQueue", DISPATCH_QUEUE_CONCURRENT);
    
    void(^block)(int) = ^(int idx){
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2.0]; //模拟耗时操作
            NSLog(@"%d === %@", idx, [NSThread currentThread]);
        }
    };
    
    dispatch_async(queue, ^{
        block(1);
    });
    
    dispatch_async(queue, ^{
        block(2);
    });
    
    dispatch_async(queue, ^{
        block(3);
    });
    
    NSLog(@"executeTasks === end");
}

//输出
2020-03-19 14:23:13.795613+0800 Demo[4562:69639] currentThread===<NSThread: 0x6000023f20c0>{number = 1, name = main}
2020-03-19 14:23:13.795744+0800 Demo[4562:69639] executeTasks === begin
2020-03-19 14:23:13.795896+0800 Demo[4562:69639] executeTasks === end
2020-03-19 14:23:15.800817+0800 Demo[4562:69752] 1 === <NSThread: 0x6000023baa40>{number = 7, name = (null)}
2020-03-19 14:23:15.800825+0800 Demo[4562:69753] 3 === <NSThread: 0x6000023abc40>{number = 6, name = (null)}
2020-03-19 14:23:15.800818+0800 Demo[4562:69755] 2 === <NSThread: 0x6000023b2c00>{number = 5, name = (null)}
2020-03-19 14:23:17.805289+0800 Demo[4562:69752] 1 === <NSThread: 0x6000023baa40>{number = 7, name = (null)}
2020-03-19 14:23:17.805308+0800 Demo[4562:69755] 2 === <NSThread: 0x6000023b2c00>{number = 5, name = (null)}
2020-03-19 14:23:17.805289+0800 Demo[4562:69753] 3 === <NSThread: 0x6000023abc40>{number = 6, name = (null)}

结果可以看到:

4.3 同步执行 + 串行队列

- (void)executeTasks
{
    NSLog(@"currentThread===%@", [NSThread currentThread]);
    NSLog(@"executeTasks === begin");
    
    dispatch_queue_t queue = dispatch_queue_create("com.company.demoQueue", DISPATCH_QUEUE_SERIAL);
    
    void(^block)(int) = ^(int idx){
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2.0]; //模拟耗时操作
            NSLog(@"%d === %@", idx, [NSThread currentThread]);
        }
    };
    
    dispatch_sync(queue, ^{
        block(1);
    });
    
    dispatch_sync(queue, ^{
        block(2);
    });
    
    dispatch_sync(queue, ^{
        block(3);
    });
    
    NSLog(@"executeTasks === end");
}

//输出
2020-03-19 14:31:24.465959+0800 Demo[4609:74437] currentThread===<NSThread: 0x600001f5e1c0>{number = 1, name = main}
2020-03-19 14:31:24.466124+0800 Demo[4609:74437] executeTasks === begin
2020-03-19 14:31:26.466482+0800 Demo[4609:74437] 1 === <NSThread: 0x600001f5e1c0>{number = 1, name = main}
2020-03-19 14:31:28.466951+0800 Demo[4609:74437] 1 === <NSThread: 0x600001f5e1c0>{number = 1, name = main}
2020-03-19 14:31:30.467850+0800 Demo[4609:74437] 2 === <NSThread: 0x600001f5e1c0>{number = 1, name = main}
2020-03-19 14:31:32.468144+0800 Demo[4609:74437] 2 === <NSThread: 0x600001f5e1c0>{number = 1, name = main}
2020-03-19 14:31:34.469573+0800 Demo[4609:74437] 3 === <NSThread: 0x600001f5e1c0>{number = 1, name = main}
2020-03-19 14:31:36.471161+0800 Demo[4609:74437] 3 === <NSThread: 0x600001f5e1c0>{number = 1, name = main}
2020-03-19 14:31:36.471428+0800 Demo[4609:74437] executeTasks === end

结果可以看到:

4.4 异步执行 + 串行队列

- (void)executeTasks
{
    NSLog(@"currentThread===%@", [NSThread currentThread]);
    NSLog(@"executeTasks === begin");
    
    dispatch_queue_t queue = dispatch_queue_create("com.company.demoQueue", DISPATCH_QUEUE_SERIAL);
    
    void(^block)(int) = ^(int idx){
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2.0]; //模拟耗时操作
            NSLog(@"%d === %@", idx, [NSThread currentThread]);
        }
    };
    
    dispatch_async(queue, ^{
        block(1);
    });
    
    dispatch_async(queue, ^{
        block(2);
    });
    
    dispatch_async(queue, ^{
        block(3);
    });
    
    NSLog(@"executeTasks === end");
}

//输出
2020-03-19 14:35:04.442828+0800 Demo[4642:77056] currentThread===<NSThread: 0x600002235ec0>{number = 1, name = main}
2020-03-19 14:35:04.442969+0800 Demo[4642:77056] executeTasks === begin
2020-03-19 14:35:04.443136+0800 Demo[4642:77056] executeTasks === end
2020-03-19 14:35:06.447142+0800 Demo[4642:77124] 1 === <NSThread: 0x600002256cc0>{number = 5, name = (null)}
2020-03-19 14:35:08.451362+0800 Demo[4642:77124] 1 === <NSThread: 0x600002256cc0>{number = 5, name = (null)}
2020-03-19 14:35:10.452756+0800 Demo[4642:77124] 2 === <NSThread: 0x600002256cc0>{number = 5, name = (null)}
2020-03-19 14:35:12.456627+0800 Demo[4642:77124] 2 === <NSThread: 0x600002256cc0>{number = 5, name = (null)}
2020-03-19 14:35:14.462057+0800 Demo[4642:77124] 3 === <NSThread: 0x600002256cc0>{number = 5, name = (null)}
2020-03-19 14:35:16.466854+0800 Demo[4642:77124] 3 === <NSThread: 0x600002256cc0>{number = 5, name = (null)}

结果可以看到:

4.5 同步执行 + 主线程

同步执行+ 主队列,若在主线程中执行,则主线程会相互等待造成死锁,而在其他线程执行不会死锁。

注意:其他线程A中同步执行+A线程,也会造成死锁。所以使用同步执行的时候,要很小心,一不留神就会死锁。

再举一个例子,主线程中任意队列同步执行一个getter,在队列中又因为要获取视图的某个属性,需要同步切回到主线程进行获取,就造成了主线程想回等待的死锁问题。同步执行一定要小心。

- (void)executeTasks
{
    NSLog(@"currentThread===%@", [NSThread currentThread]);
    NSLog(@"executeTasks === begin");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    void(^block)(int) = ^(int idx){
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2.0]; //模拟耗时操作
            NSLog(@"%d === %@", idx, [NSThread currentThread]);
        }
    };
    
    dispatch_sync(queue, ^{
        block(1);
    });
    
    dispatch_sync(queue, ^{
        block(2);
    });
    
    dispatch_sync(queue, ^{
        block(3);
    });
    
    NSLog(@"executeTasks === end");
}

//输出
2020-03-19 15:14:15.635537+0800 Demo[7222:107473] currentThread===<NSThread: 0x6000003b2740>{number = 1, name = main}
2020-03-19 15:14:15.636617+0800 Demo[7222:107473] executeTasks === begin
//执行到同步任务时崩溃

我们在其他线程执行这个任务:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [self executeTasks];
});

//输出
2020-03-19 15:20:02.691105+0800 Demo[8404:117730] currentThread===<NSThread: 0x60000063c580>{number = 5, name = (null)}
2020-03-19 15:20:02.691269+0800 Demo[8404:117730] executeTasks === begin
2020-03-19 15:20:02.691499+0800 Demo[8404:117730] executeTasks === end
2020-03-19 15:20:04.754529+0800 Demo[8404:117596] 1 === <NSThread: 0x600000658f80>{number = 1, name = main}
2020-03-19 15:20:06.754974+0800 Demo[8404:117596] 1 === <NSThread: 0x600000658f80>{number = 1, name = main}
2020-03-19 15:20:08.755508+0800 Demo[8404:117596] 2 === <NSThread: 0x600000658f80>{number = 1, name = main}
2020-03-19 15:20:10.757111+0800 Demo[8404:117596] 2 === <NSThread: 0x600000658f80>{number = 1, name = main}
2020-03-19 15:20:12.758283+0800 Demo[8404:117596] 3 === <NSThread: 0x600000658f80>{number = 1, name = main}
2020-03-19 15:20:14.759476+0800 Demo[8404:117596] 3 === <NSThread: 0x600000658f80>{number = 1, name = main}

结果可以看到:

其他线程中执行时不存在主线程相互等待的问题,所以不会出现死锁崩溃。

4.6 异步执行 + 主线程

- (void)executeTasks
{
    NSLog(@"currentThread===%@", [NSThread currentThread]);
    NSLog(@"executeTasks === begin");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    void(^block)(int) = ^(int idx){
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2.0]; //模拟耗时操作
            NSLog(@"%d === %@", idx, [NSThread currentThread]);
        }
    };
    
    dispatch_async(queue, ^{
        block(1);
    });
    
    dispatch_async(queue, ^{
        block(2);
    });
    
    dispatch_async(queue, ^{
        block(3);
    });
    
    NSLog(@"executeTasks === end");
}
//输出
2020-03-19 15:16:46.857599+0800 Demo[7285:110849] currentThread===<NSThread: 0x600001c27a40>{number = 1, name = main}
2020-03-19 15:16:46.857786+0800 Demo[7285:110849] executeTasks === begin
2020-03-19 15:16:46.857986+0800 Demo[7285:110849] executeTasks === end
2020-03-19 15:16:48.990737+0800 Demo[7285:110849] 1 === <NSThread: 0x600001c27a40>{number = 1, name = main}
2020-03-19 15:16:50.992020+0800 Demo[7285:110849] 1 === <NSThread: 0x600001c27a40>{number = 1, name = main}
2020-03-19 15:16:52.993452+0800 Demo[7285:110849] 2 === <NSThread: 0x600001c27a40>{number = 1, name = main}
2020-03-19 15:16:54.993844+0800 Demo[7285:110849] 2 === <NSThread: 0x600001c27a40>{number = 1, name = main}
2020-03-19 15:16:56.995228+0800 Demo[7285:110849] 3 === <NSThread: 0x600001c27a40>{number = 1, name = main}
2020-03-19 15:16:58.996538+0800 Demo[7285:110849] 3 === <NSThread: 0x600001c27a40>{number = 1, name = main}

结果可以看到:

5. GCD的其他方法

5.1 栅栏方法:dispath_barrier_async

我们有时需要异步执行两组操作,而且第一组操作执行完后,才能开始执行第二组操作。这时,我们就需要一个相当于栅栏一样的方法dispath_barrier_async将两组操纵分隔开。dispath_barrier_async函数会等待前面的并发队列中的任务执行完后,再将指定的任务追加到异步队列中。在dispath_barrier_async执行完毕后,再执行之后的操作。

- (void)executeTasks
{
    NSLog(@"currentThread===%@", [NSThread currentThread]);
    NSLog(@"executeTasks === begin");
    
    dispatch_queue_t queue = dispatch_queue_create("com.company.demoQueue", DISPATCH_QUEUE_CONCURRENT);
    
    void(^block)(NSString *) = ^(NSString *info){
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2.0]; //模拟耗时操作
            NSLog(@"%@ === %@", info, [NSThread currentThread]);
        }
    };
    
    dispatch_async(queue, ^{
        block(@"1");
    });
    
    dispatch_async(queue, ^{
        block(@"2");
    });
    
    dispatch_barrier_async(queue, ^{
        block(@"barrier");
    });
    
    dispatch_async(queue, ^{
        block(@"3");
    });
    
    dispatch_async(queue, ^{
        block(@"4");
    });
    
    NSLog(@"executeTasks === end");
}

//输出
2020-03-20 09:17:48.586161+0800 Demo[20665:418386] currentThread===<NSThread: 0x60000063a140>{number = 1, name = main}
2020-03-20 09:17:48.586323+0800 Demo[20665:418386] executeTasks === begin
2020-03-20 09:17:48.586528+0800 Demo[20665:418386] executeTasks === end
2020-03-20 09:17:50.588100+0800 Demo[20665:418626] 2 === <NSThread: 0x6000006f8880>{number = 8, name = (null)}
2020-03-20 09:17:50.588155+0800 Demo[20665:418621] 1 === <NSThread: 0x600000666300>{number = 6, name = (null)}
2020-03-20 09:17:52.591849+0800 Demo[20665:418621] 1 === <NSThread: 0x600000666300>{number = 6, name = (null)}
2020-03-20 09:17:52.591872+0800 Demo[20665:418626] 2 === <NSThread: 0x6000006f8880>{number = 8, name = (null)}
2020-03-20 09:17:54.593106+0800 Demo[20665:418626] barrier === <NSThread: 0x6000006f8880>{number = 8, name = (null)}
2020-03-20 09:17:56.596775+0800 Demo[20665:418626] barrier === <NSThread: 0x6000006f8880>{number = 8, name = (null)}
2020-03-20 09:17:58.597505+0800 Demo[20665:418626] 3 === <NSThread: 0x6000006f8880>{number = 8, name = (null)}
2020-03-20 09:17:58.597550+0800 Demo[20665:418621] 4 === <NSThread: 0x600000666300>{number = 6, name = (null)}
2020-03-20 09:18:00.598899+0800 Demo[20665:418626] 3 === <NSThread: 0x6000006f8880>{number = 8, name = (null)}
2020-03-20 09:18:00.598904+0800 Demo[20665:418621] 4 === <NSThread: 0x600000666300>{number = 6, name = (null)}

所有任务都是在executeTasks === beginexecuteTasks === end之后执行的。且栅栏函数将任务分隔开了。先执行栅栏之前的任务,再执行栅栏之后的任务。

5.2 延时执行方法:dispatch_after

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

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

- (void)after
{
    NSLog(@"after === %@", [NSThread currentThread]);
    NSLog(@"after === begin");
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"after === %@", [NSThread currentThread]);
    });
    
    NSLog(@"after === end");
}

//输出
2020-03-20 09:26:54.794463+0800 Demo[20750:426197] currentThread===<NSThread: 0x600000f062c0>{number = 1, name = main}
2020-03-20 09:26:54.794601+0800 Demo[20750:426197] after===begin
2020-03-20 09:26:54.794722+0800 Demo[20750:426197] after===end
2020-03-20 09:26:56.992372+0800 Demo[20750:426197] after === <NSThread: 0x600000f062c0>{number = 1, name = main}

我们看到在executeTasks === begin之后大约2秒的时间,打印了after === <NSThread: 0x600000f062c0>{number = 1, name = main}

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

我们在创建单例或者这个程序运行期间只执行一次的代码时就需要用到dispatch_once。它不经能保证代码只运行一次,而且在多线程环境下,也可以保证线程安全。

- (void)once
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //只执行一次的代码
    });
}

5.4 快速迭代:dispatch_apply

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

如果是在串行队列中使用dispatch_apply,那么就和for循环一样,按顺序同步执行。但就失去了这个函数的意义。

我们可以利用并发队列进行异步执行。比如遍历0~5这个6个数字,for循环是每次取出一个元素逐个遍历。dispatch_apply则在多个线程中并发执行遍历。

无论是在串行/并行队列,dispatch_apply都会等待全部任务执行完毕,这点就像是同步操作,也想队列组中的dispatch_group_wait方法。

- (void)apply
{
    NSLog(@"currentThread === %@", [NSThread currentThread]);
    NSLog(@"apply === begin");
    
    dispatch_queue_t queue = dispatch_queue_create("com.company.demoQueue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_apply(6, queue, ^(size_t idx) {
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2.0]; //模拟耗时操作
            NSLog(@"%zd === %d === %@", idx, i, [NSThread currentThread]);
        }
    });
    
    NSLog(@"apply === end");
}

//输出
2020-03-20 09:51:11.948382+0800 Demo[21070:444945] currentThread === <NSThread: 0x600000e1e880>{number = 1, name = main}
2020-03-20 09:51:11.948555+0800 Demo[21070:444945] apply === begin
2020-03-20 09:51:13.949125+0800 Demo[21070:444945] 0 === 0 === <NSThread: 0x600000e1e880>{number = 1, name = main}
2020-03-20 09:51:13.949141+0800 Demo[21070:445027] 2 === 0 === <NSThread: 0x600000e4c140>{number = 4, name = (null)}
2020-03-20 09:51:13.949130+0800 Demo[21070:445024] 3 === 0 === <NSThread: 0x600000e94540>{number = 7, name = (null)}
2020-03-20 09:51:13.949212+0800 Demo[21070:445026] 1 === 0 === <NSThread: 0x600000e57880>{number = 3, name = (null)}
2020-03-20 09:51:15.949888+0800 Demo[21070:445024] 3 === 1 === <NSThread: 0x600000e94540>{number = 7, name = (null)}
2020-03-20 09:51:15.949896+0800 Demo[21070:445027] 2 === 1 === <NSThread: 0x600000e4c140>{number = 4, name = (null)}
2020-03-20 09:51:15.949934+0800 Demo[21070:445026] 1 === 1 === <NSThread: 0x600000e57880>{number = 3, name = (null)}
2020-03-20 09:51:15.949908+0800 Demo[21070:444945] 0 === 1 === <NSThread: 0x600000e1e880>{number = 1, name = main}
2020-03-20 09:51:17.951477+0800 Demo[21070:445024] 4 === 0 === <NSThread: 0x600000e94540>{number = 7, name = (null)}
2020-03-20 09:51:17.951477+0800 Demo[21070:445026] 5 === 0 === <NSThread: 0x600000e57880>{number = 3, name = (null)}
2020-03-20 09:51:19.953058+0800 Demo[21070:445024] 4 === 1 === <NSThread: 0x600000e94540>{number = 7, name = (null)}
2020-03-20 09:51:19.953059+0800 Demo[21070:445026] 5 === 1 === <NSThread: 0x600000e57880>{number = 3, name = (null)}
2020-03-20 09:51:19.953419+0800 Demo[21070:444945] apply === end

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

5.5 队列组:dispatch_group

又是我们有这样的需求:异步执行多个耗时任务,但所有任务执行完毕之后,回到主线程执行任务。这是我们就需要用到dispatch_group

dispatch_group_async把任务放到队列中,再把队列放入队列组中。或者使用队列组的dispatch_group_enterdispatch_group_leave组合来实现dispatch_group_async

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

5.5.1 dispatch_group_notify
- (void)group
{
    NSLog(@"currentThread === %@", [NSThread currentThread]);
    NSLog(@"group === begin");
    
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create("com.company.demoQueue", DISPATCH_QUEUE_CONCURRENT);
    
    void(^block)(NSString *) = ^(NSString *info){
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2.0]; //模拟耗时操作
            NSLog(@"%@ === %@", info, [NSThread currentThread]);
        }
    };
    
    dispatch_group_async(group, queue, ^{
        block(@"1");
    });
    
    dispatch_group_async(group, queue, ^{
        block(@"2");
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        block(@"3");
        NSLog(@"group === end");
    });
}

//输出
2020-03-20 10:11:37.108887+0800 Demo[21219:456144] currentThread === <NSThread: 0x600003d2e1c0>{number = 1, name = main}
2020-03-20 10:11:37.109017+0800 Demo[21219:456144] group === begin
2020-03-20 10:11:39.114315+0800 Demo[21219:456277] 1 === <NSThread: 0x600003d79940>{number = 6, name = (null)}
2020-03-20 10:11:39.114385+0800 Demo[21219:456267] 2 === <NSThread: 0x600003d4c900>{number = 7, name = (null)}
2020-03-20 10:11:41.118725+0800 Demo[21219:456277] 2 === <NSThread: 0x600003d79940>{number = 7, name = (null)}
2020-03-20 10:11:41.118731+0800 Demo[21219:456267] 1 === <NSThread: 0x600003d4c900>{number = 6, name = (null)}
2020-03-20 10:11:43.119309+0800 Demo[21219:456144] 3 === <NSThread: 0x600003d2e1c0>{number = 1, name = main}
2020-03-20 10:11:45.120661+0800 Demo[21219:456144] 3 === <NSThread: 0x600003d2e1c0>{number = 1, name = main}
2020-03-20 10:11:45.129280+0800 Demo[21219:456144] group === end

当所有任务都执行完毕之后,才执行dispatch_group_notify中的任务。

5.5.2 dispatch_group_wait
- (void)group
{
    NSLog(@"currentThread === %@", [NSThread currentThread]);
    NSLog(@"group === begin");
    
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create("com.company.demoQueue", DISPATCH_QUEUE_CONCURRENT);
    
    void(^block)(NSString *) = ^(NSString *info){
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2.0]; //模拟耗时操作
            NSLog(@"%@ === %@", info, [NSThread currentThread]);
        }
    };
    
    dispatch_group_async(group, queue, ^{
        block(@"1");
    });
    
    dispatch_group_async(group, queue, ^{
        block(@"2");
    });
    
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    
    NSLog(@"group === end");
}

//输出
2020-03-20 10:13:57.891391+0800 Demo[21241:458165] currentThread === <NSThread: 0x6000010e3200>{number = 1, name = main}
2020-03-20 10:13:57.891533+0800 Demo[21241:458165] group === begin
2020-03-20 10:13:59.896610+0800 Demo[21241:458255] 1 === <NSThread: 0x6000010a2640>{number = 4, name = (null)}
2020-03-20 10:13:59.896612+0800 Demo[21241:458254] 2 === <NSThread: 0x600001082580>{number = 7, name = (null)}
2020-03-20 10:14:01.901907+0800 Demo[21241:458255] 2 === <NSThread: 0x6000010a2640>{number = 7, name = (null)}
2020-03-20 10:14:01.901912+0800 Demo[21241:458254] 1 === <NSThread: 0x600001082580>{number = 4, name = (null)}
2020-03-20 10:14:01.902625+0800 Demo[21241:458165] group === end

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

5.5.3 dispatch_group_enter、dispatch_group_leave
- (void)group
{
    NSLog(@"currentThread === %@", [NSThread currentThread]);
    NSLog(@"group === begin");
    
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create("com.company.demoQueue", DISPATCH_QUEUE_CONCURRENT);
    
    void(^block)(NSString *) = ^(NSString *info){
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2.0]; //模拟耗时操作
            NSLog(@"%@ === %@", info, [NSThread currentThread]);
        }
    };
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        block(@"1");
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        block(@"2");
        dispatch_group_leave(group);
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        block(@"3");
        NSLog(@"group === end");
    });
}

//输出
2020-03-20 10:20:02.482317+0800 Demo[21318:463789] currentThread === <NSThread: 0x600003f421c0>{number = 1, name = main}
2020-03-20 10:20:02.482468+0800 Demo[21318:463789] group === begin
2020-03-20 10:20:04.486020+0800 Demo[21318:463890] 1 === <NSThread: 0x600003f18080>{number = 5, name = (null)}
2020-03-20 10:20:04.486020+0800 Demo[21318:463883] 2 === <NSThread: 0x600003f2a300>{number = 7, name = (null)}
2020-03-20 10:20:06.488545+0800 Demo[21318:463883] 2 === <NSThread: 0x600003f2a300>{number = 7, name = (null)}
2020-03-20 10:20:06.488545+0800 Demo[21318:463890] 1 === <NSThread: 0x600003f18080>{number = 5, name = (null)}
2020-03-20 10:20:08.490275+0800 Demo[21318:463789] 3 === <NSThread: 0x600003f421c0>{number = 1, name = main}
2020-03-20 10:20:10.490804+0800 Demo[21318:463789] 3 === <NSThread: 0x600003f421c0>{number = 1, name = main}
2020-03-20 10:20:10.490994+0800 Demo[21318:463789] group === end

当所有任务都执行完毕之后,才执行dispatch_group_notify中的任务。使用dispatch_group_enterdispatch_group_leave组合其实等同于dispatch_group_async

5.6 信号量:dispatch_semaphore

信号量是指Dispatch Semaphore,持有计数的型号。类似于一个开关,计数为0时等待,不可执行;技术大于等于1时,计数减1且不等待可以执行。Dispatch Semaphore有3个函数:

在开发中信号量主要用于:

5.6.1 线程同步
- (void)semaphore
{
    NSLog(@"currentThread === %@", [NSThread currentThread]);
    NSLog(@"semaphore === begin");
    
    dispatch_queue_t queue = dispatch_queue_create("com.company.demoQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    
    void(^block)(NSString *) = ^(NSString *info){
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2.0]; //模拟耗时操作
            NSLog(@"%@ === %d === %@", info, i, [NSThread currentThread]);
        }
    };
    
    dispatch_async(queue, ^{
        block(@"1");
        dispatch_semaphore_signal(semaphore);
    });
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    
    NSLog(@"semaphore === end");
}

//输出
2020-03-20 10:34:41.027832+0800 Demo[21419:474793] currentThread === <NSThread: 0x600000ce9a40>{number = 1, name = main}
2020-03-20 10:34:41.027974+0800 Demo[21419:474793] semaphore === begin
2020-03-20 10:34:43.032498+0800 Demo[21419:474868] 1 === 0 === <NSThread: 0x600000cbf380>{number = 6, name = (null)}
2020-03-20 10:34:45.033584+0800 Demo[21419:474868] 1 === 1 === <NSThread: 0x600000cbf380>{number = 6, name = (null)}
2020-03-20 10:34:45.033795+0800 Demo[21419:474793] semaphore === end

可以看到我们semaphore === end是在最后打印的。因为异步任务不做等待,接着执行dispatch_semaphore_wait方法,此时信号总量是0,所以阻塞当前线程,进入等待。异步任务执行完,执行dispatch_semaphore_signal,信号总量加1,dispatch_semaphore_wait减1,被阻塞的线程恢复继续执行。这样就实现了线程同步,并将异步任务转换为了同步任务。

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

我们用经典的火车卖票来模拟,线程安全和解决线程同步问题:

总共50张火车票,有2个网络窗口同时卖票,卖完为止。

非线程安全

- (void)initTicketStatusNotSafe {
    NSLog(@"currentThread === %@", [NSThread currentThread]);
    NSLog(@"semaphore === begin");
    
    self.ticketCount = 50;
    
    dispatch_queue_t queue0 = dispatch_queue_create("com.company.demoQueue0", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue1 = dispatch_queue_create("com.company.demoQueue1", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(queue0, ^{
        [self saleTicketNotSafe];
    });
    
    dispatch_async(queue1, ^{
        [self saleTicketNotSafe];
    });
}

- (void)saleTicketNotSafe {
    while (true) {
        if (self.ticketCount > 0) {
            self.ticketCount--;
            NSLog(@"剩余票数:%ld 窗口:%@", self.ticketCount, [NSThread currentThread]);
            [NSThread sleepForTimeInterval:0.2];
        } else {
            NSLog(@"所有火车票已售完");
            break;
        }
    }
}

//输出
2020-03-20 10:51:26.974019+0800 Demo[21594:486661] currentThread === <NSThread: 0x600002cb81c0>{number = 1, name = main}
2020-03-20 10:51:26.974272+0800 Demo[21594:486661] semaphore === begin
2020-03-20 10:51:26.974502+0800 Demo[21594:486843] 剩余票数:48 窗口:<NSThread: 0x600002c3d200>{number = 7, name = (null)}
2020-03-20 10:51:26.974502+0800 Demo[21594:486842] 剩余票数:49 窗口:<NSThread: 0x600002ce9c00>{number = 4, name = (null)}
2020-03-20 10:51:27.180562+0800 Demo[21594:486842] 剩余票数:47 窗口:<NSThread: 0x600002ce9c00>{number = 4, name = (null)}
2020-03-20 10:51:27.180618+0800 Demo[21594:486843] 剩余票数:46 窗口:<NSThread: 0x600002c3d200>{number = 7, name = (null)}
...

可以看到非线程安全的情况下得到的剩余票数是错乱的。

线程安全

- (void)initTicketStatusSafe {
    NSLog(@"currentThread === %@", [NSThread currentThread]);
    NSLog(@"semaphore === begin");
    
    self.ticketCount = 50;
    self.semaphore = dispatch_semaphore_create(1);
    
    dispatch_queue_t queue0 = dispatch_queue_create("com.company.demoQueue0", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue1 = dispatch_queue_create("com.company.demoQueue1", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(queue0, ^{
        [self saleTicketSafe];
    });
    
    dispatch_async(queue1, ^{
        [self saleTicketSafe];
    });
}

- (void)saleTicketSafe {
    while (true) {
        //相当于加锁
        dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
        if (self.ticketCount > 0) {
            self.ticketCount--;
            NSLog(@"剩余票数:%ld 窗口:%@", self.ticketCount, [NSThread currentThread]);
            [NSThread sleepForTimeInterval:0.2];
            //相当于解锁
            dispatch_semaphore_signal(self.semaphore);
        } else {
            NSLog(@"所有火车票已售完");
            //相当于解锁
            dispatch_semaphore_signal(self.semaphore);
            break;
        }
    }
}

//输出
2020-03-20 11:00:25.476416+0800 Demo[21685:492954] currentThread === <NSThread: 0x600003e25280>{number = 1, name = main}
2020-03-20 11:00:25.476575+0800 Demo[21685:492954] semaphore === begin
2020-03-20 11:00:25.476897+0800 Demo[21685:493065] 剩余票数:49 窗口:<NSThread: 0x600003e40400>{number = 6, name = (null)}
2020-03-20 11:00:25.681902+0800 Demo[21685:493072] 剩余票数:48 窗口:<NSThread: 0x600003e71140>{number = 5, name = (null)}
2020-03-20 11:00:25.887811+0800 Demo[21685:493065] 剩余票数:47 窗口:<NSThread: 0x600003e40400>{number = 6, name = (null)}
...
2020-03-20 11:00:35.045573+0800 Demo[21685:493072] 剩余票数:2 窗口:<NSThread: 0x600003e71140>{number = 5, name = (null)}
2020-03-20 11:00:35.248422+0800 Demo[21685:493065] 剩余票数:1 窗口:<NSThread: 0x600003e40400>{number = 6, name = (null)}
2020-03-20 11:00:35.452757+0800 Demo[21685:493072] 剩余票数:0 窗口:<NSThread: 0x600003e71140>{number = 5, name = (null)}
2020-03-20 11:00:35.654837+0800 Demo[21685:493065] 所有火车票已售完
2020-03-20 11:00:35.655140+0800 Demo[21685:493072] 所有火车票已售完

考虑线程安全的情况下,使用信号量加锁解锁后,得到的票数顺序是正确的,就解决了多线程的同步问题。

上一篇 下一篇

猜你喜欢

热点阅读