美文网首页
GCD总结篇

GCD总结篇

作者: 收纳箱 | 来源:发表于2020-03-20 11:03 被阅读0次

    GCD总结篇

    1.GCD的优势

    • 可用于多核的并行运算
    • 会自动利用更多的CPU内核
    • 会自动管理线程的生命周期(创建线程、调度任务、销毁线程)

    2. GCD任务和队列

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

    1. 任务

      • 需要执行的代码逻辑。

      • GCD中放在block中。

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

        • 同步执行(sync):

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

        • 异步执行(async):

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

    2. 队列(Dispatch Queue)

      • 存放任务的队列,任务执行的等待队列。
    • 采用FIFO(先进先出)的原则,新的任务总是被插入到队列的末尾,执行任务的时候总是从队列的头部开始执行。开始执行一个任务,就从队列中释放一个任务,下一个任务来到队列头部。

      • GCD中有两种队列:串行队列和并行队列。两者的区别是:执行顺序不同,开启的线程数不同。

        • 串行队列(Serial Dispatch Queue):

          只开启一个线程,每次只有一个任务被执行。一个任务执行完之后,再执行下一个任务。

        • 并行队列(Concurrent Dispatch Queue):

          可以开启多个线程,并同时并发地执行任务。

          注意:并发队列的并发功能只有在异步(dispatch_async)函数下才有效。

    3. GCD使用步骤

    • 创建一个队列(串行或者并行队列)
    • 将任务添加到任务的等待队列中,系统就会根据任务类型执行任务(同步执行或异步执行)

    3.1 队列的创建/获取

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

    • 第一个参数表示队列的唯一标识符,用于DEBUG,可空。名称推荐使用应用程序ID这种域名;
    • 第二个参数用来表明创建的是串行队列还是并发队列:DISPATCH_QUEUE_SERIALDISPATCH_QUEUE_CONCURRENT
    //串行队列
    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_get_main_queue获取主队列。

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

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

    • 第一个参数表示队列的优先级,一般使用DISPATCH_QUEUE_PRIORITY_DEFAULT
    • 第二个参数暂时没用,一般使用0
    //全局并发队列
    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
    

    结果可以看到:

    • 所有任务都是在当前线程(主线程)中执行的,没有开启新线程。(同步执行不具备开启新线程的能力)
    • 所有任务都是在executeTasks === beginexecuteTasks === 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)}
    

    结果可以看到:

    • 除了当前线程(主线程),系统又开启了3个线程,并且任务是同时/交替执行的。(异步执行具有开启新线程的能力,且并发队列可以开启多个线程,并发执行多个任务)
    • 所有任务都是在executeTasks === beginexecuteTasks === end之后执行的。说明当前线程任务之后的代码没有等待,而是直接开启了新线程,在新线程中执行任务。(异步执行不需要等待,可以继续执行当前线程的其他代码)

    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
    

    结果可以看到:

    • 所有任务都是在当前线程(主线程)中执行的,没有开启新线程。(同步执行不具备开启新线程的能力)
    • 所有任务都是在executeTasks === beginexecuteTasks === 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)}
    

    结果可以看到:

    • 开启了一个新线程。(异步执行具备开启新线程的能力,但串行队列只开启一个线程)
    • 所有任务都是在executeTasks === beginexecuteTasks === end之后执行的。(说明当前线程任务之后的代码没有等待,而是直接开启了新线程,在新线程中执行任务。(异步执行不需要等待,可以继续执行当前线程的其他代码)
    • 任务都是按顺序执行的。(串行队列每次只有一个任务执行)

    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}
    

    结果可以看到:

    • 所有任务都是在主线程(非当前线程)中执行的。(所有放到主队列中的任务都放在主线程中执行)
    • 所有任务都是在executeTasks === beginexecuteTasks === end之后执行的。(说明当前线程任务之后的代码没有等待,而是直接开启了新线程,在新线程中执行任务。(异步执行不需要等待,可以继续执行当前线程的其他代码)
    • 任务都是按顺序执行的。(主队列是串行队列每次只有一个任务执行)

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

    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}
    

    结果可以看到:

    • 所有任务都是在主线程中执行的。
    • 所有任务都是在executeTasks === beginexecuteTasks === end之后执行的。(说明当前线程任务之后的代码没有等待,而是直接开启了新线程,在新线程中执行任务。(异步执行不需要等待,可以继续执行当前线程的其他代码)
    • 任务都是按顺序执行的。(主队列是串行队列每次只有一个任务执行)

    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个函数:

    • dispatch_semaphore_create:创建一个信号量且初始化信号的总量。
    • dispatch_semaphore_signal:发送一个信号,让信号总量加1。
    • dispatch_semaphore_wait:可以使信号总量减1,当信号总量为0时,阻塞所在线程,一直等待,否则正常执行。

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

    • 保持线程同步,将异步执行任务转换为同步执行任务。
    • 保证线程安全,为线程加锁。
    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] 所有火车票已售完
    

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

    相关文章

      网友评论

          本文标题:GCD总结篇

          本文链接:https://www.haomeiwen.com/subject/opodyhtx.html