美文网首页
iOS多线程之:GCD

iOS多线程之:GCD

作者: DevHuangjb | 来源:发表于2019-01-21 21:04 被阅读0次

我个人在开发中,遇到并发的操作,比较习惯用GCD来实现。原因很简单,高效!方便!

什么是GCD?

百度百科:Grand Central Dispatch (GCD)是Apple开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并行任务。

从字面上来看,GCD意思是“伟大的中枢调度器”。是纯C语言的API,提供了非常强大的函数。

GCD的优势
  • GCD是苹果为多核运算提出的解决方案,可以更好的利用CPU的多核优势
  • GCD会自动管理线程的生命周期(创建线程,调度任务,销毁线程)
  • 方便的使用API,程序员只需要专注于要执行的任务
任务和队列

GCD开发中,两个很重要的概念就是任务和队列,核心就是把要执行的任务添加到队列里面

队列:用于管理任务的数据结构,队列遵循严格的FIFO原则,即先添加的任务先执行,后添加的任务后执行。

  • 串行队列:只会开辟一条子线程,队列上面的任务在这条线程上面一个一个依次的执行
  • 并行队列:可以开辟多条线程,并行的执行队列上面的任务。

主队列:主队列是一种特殊的串行队列,所有放在主队列上面的任务都会在主线程当中执行。所以,主要用于操作UI元素的任务。使用dispatch_get_main_queue()来获取主队列

全局队列:苹果默认创建的全局的并发队列。

 获取全局队列:dispatch_get_global_queue(long identifier, unsigned long flags );
 identifier:为队列指定的优先级
 flags:保留参数,必须传0,传其他值返回的nil。
 * iOS8后,苹果推出并推荐使用qos_class来指定全局队列的优先级
 * 以下优先级依次降低
 *  - QOS_CLASS_USER_INTERACTIVE
 *  - QOS_CLASS_USER_INITIATED
 *  - QOS_CLASS_DEFAULT
 *  - QOS_CLASS_UTILITY
 *  - QOS_CLASS_BACKGROUND
 *
 * 仍然可以使用priority来指定全局队列的优先级,priority和qos_class的映射关系如下:
 *  - DISPATCH_QUEUE_PRIORITY_HIGH:         QOS_CLASS_USER_INITIATED
 *  - DISPATCH_QUEUE_PRIORITY_DEFAULT:      QOS_CLASS_DEFAULT
 *  - DISPATCH_QUEUE_PRIORITY_LOW:          QOS_CLASS_UTILITY
 *  - DISPATCH_QUEUE_PRIORITY_BACKGROUND:   QOS_CLASS_BACKGROUND

任务:就是我们要执行的操作。

任务执行的两种方式:

  • 同步执行:

    • dispatch_sync同步的添加任务到队列中,队列中后面的任务必须等到同步执行的任务执行结束后才能执行;
    • 不会开辟子线程,任务是在当前线程执行的
  • 异步执行:

    • dispatch_asyn异步的添加任务到队列中,队列中后面的任务不用等到异步执行的任务结束就能执行。

    • 具备开启子线程执行任务的能力

    • 异步执行具备开启线程的能力,但不一定都开启子线程。在主队列不允许开新线程。

GCD的使用

根据两种类型的队列和两种不同的执行方式,我们可以有四种组合来使用GCD

  • 在串行队列上面同步执行任务

  • 在串行队列上面异步执行任务

  • 在并发队列上面同步执行任务

  • 在并发队列上面异步执行任务

  • 在主队列上面同步执行任务

  • 在主队列上面异步执行任务

以下的测试方法都是在 主线程 执行:

  1. 在串行队列上面同步执行任务:不开辟子线程,任务顺序一个一个在当前线程执行

    - (void)sync_serial {
        dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
        for (int i = 0; i < 3; i++) {
            dispatch_sync(queue, ^{
                NSLog(@"%@ %d", [NSThread currentThread], i);
            });
        }
    }
    控制台输出:
    2019-01-06 15:38:49.235601+0800 GCD_Demo[1665:267519] <NSThread: 0x6000000a5400>{number = 1, name = main} 0
    2019-01-06 15:38:49.235734+0800 GCD_Demo[1665:267519] <NSThread: 0x6000000a5400>{number = 1, name = main} 1
    2019-01-06 15:38:49.235838+0800 GCD_Demo[1665:267519] <NSThread: 0x6000000a5400>{number = 1, name = main} 2
    
  1. 在串行队列上面异步执行任务:只会开辟一条子线程,任务在子线程依次执行

    - (void)async_serial {
        dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
        for (int i = 0; i < 3; i++) {
            dispatch_async(queue, ^{
                NSLog(@"%@ %d", [NSThread currentThread], i);
            });
        }
    }
    控制台输出:
    2019-01-06 15:34:52.334973+0800 GCD_Demo[1594:253049] <NSThread: 0x600003ce1f40>{number = 3, name = (null)} 0
    2019-01-06 15:34:52.335465+0800 GCD_Demo[1594:253049] <NSThread: 0x600003ce1f40>{number = 3, name = (null)} 1
    2019-01-06 15:34:52.335570+0800 GCD_Demo[1594:253049] <NSThread: 0x600003ce1f40>{number = 3, name = (null)} 2
    
  1. 在并发队列上面同步执行任务:不开辟子线程,任务顺序一个一个在当前线程执行
- (void)sync_concurrent {
    dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i < 3; i++) {
        dispatch_sync(queue, ^{
            NSLog(@"%@ %d", [NSThread currentThread], i);
        });
    }
}
控制台输出:
2019-01-06 15:36:59.044932+0800 GCD_Demo[1641:260902] <NSThread: 0x600001e7e940>{number = 1, name = main} 0
2019-01-06 15:36:59.045100+0800 GCD_Demo[1641:260902] <NSThread: 0x600001e7e940>{number = 1, name = main} 1
2019-01-06 15:36:59.045196+0800 GCD_Demo[1641:260902] <NSThread: 0x600001e7e940>{number = 1, name = main} 2
  1. 在并发队列上面异步执行任务:会开辟多个线程同时执行多个任务,具体开辟的线程数由内核决定
- (void)async_concurrent {
    dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i < 3; i++) {
        dispatch_async(queue, ^{
            NSLog(@"%@ %d", [NSThread currentThread], i);
        });
    }
}
控制台输出:
2019-01-06 15:40:08.082757+0800 GCD_Demo[1695:274396] <NSThread: 0x600000dba740>{number = 5, name = (null)} 2
2019-01-06 15:40:08.082768+0800 GCD_Demo[1695:274397] <NSThread: 0x600000db6ac0>{number = 4, name = (null)} 1
2019-01-06 15:40:08.082775+0800 GCD_Demo[1695:274395] <NSThread: 0x600000da2980>{number = 3, name = (null)} 0
  1. 在主队列上面异步执行任务:不开辟子线程,任务顺序一个一个在主线程执行

    - (void)async_main_queue {
        dispatch_queue_t queue = dispatch_get_main_queue();
        for (int i = 0; i < 3; i++) {
            dispatch_async(queue, ^{
                NSLog(@"%@ %d", [NSThread currentThread], i);
            });
        }
    }
    控制台输出:
    2019-01-06 15:46:01.790810+0800 GCD_Demo[1797:294279] <NSThread: 0x600002104dc0>{number = 1, name = main} 0
    2019-01-06 15:46:01.790974+0800 GCD_Demo[1797:294279] <NSThread: 0x600002104dc0>{number = 1, name = main} 1
    2019-01-06 15:46:01.791075+0800 GCD_Demo[1797:294279] <NSThread: 0x600002104dc0>{number = 1, name = main} 2
    
  1. 在主队列上面同步执行任务:会出现死锁的情况

  2. - (void)sync_main_queue {
        dispatch_queue_t queue = dispatch_get_main_queue();
        for (int i = 0; i < 3; i++) {
            dispatch_sync(queue, ^{
                NSLog(@"%@ %d", [NSThread currentThread], i);
            });
        }
    }
    控制台没有输出,程序crash
    

全局队列属于并发队列,使用起来和并发队列没什么差别,这里不再另行讨论

关于在主队列上面同步执行任务出现死锁的讨论

有的同学可能会困惑,上面讨论的GCD六种使用方式中,为什么方式6会出现死锁而方式1不会呢?

之所以会出现这种困惑,主要是队列和线程的概念没有分清。

线程是执行任务的,队列是保存任务的,队列遵循严格的FIFO原则。GCD根据队列的类型按照不同的方式取出任务放到线程执行。出现死锁的原因是队列的阻塞而不是线程的阻塞。

下面我画了张图来分析死锁的情况:


死锁分析.jpg

下面来看看串行同步不会引起死锁的分析:


串行同步分析.png
栅栏效应:

dispatch_barrier针对的是并发队列,但是注意:这个队列不可以是全局队列!!!

barrier就是栅栏的意思,dispatch_barrier可以保证当前队列中只有一个任务在执行中,在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行。

dispatch_barrier提供了两种方式来保证只有一个任务在执行中

  • dispatch_barrier_async
  • dispatch_barrier_sync

区别:dispatch_barrier_async不用等到任务执行完毕就能往下执行,dispatch_barrier_sync要等到添加的任务执行完毕才能继续往下执行。

下面来看我写的一个dispatch_barrier_async_demo和打印效果:

- (void)dispatch_barrier_async_demo {
    dispatch_queue_t queue = dispatch_queue_create("gcdtest.rongfzh.yc", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"dispatch_async1");
    });
    dispatch_barrier_async(queue, ^{
        NSLog(@"dispatch_barrier_async_start");
        [NSThread sleepForTimeInterval:4];
        NSLog(@"dispatch_barrier_async_end");
        
    });
    dispatch_async(queue, ^{
        NSLog(@"dispatch_async3");
    });
}
控制台打印:
2019-01-13 12:22:28.851753+0800 GCD_Demo[1392:110129] after diapatch barrier
2019-01-13 12:22:28.851754+0800 GCD_Demo[1392:110169] task1
2019-01-13 12:22:28.851869+0800 GCD_Demo[1392:110169] task2 start
2019-01-13 12:22:32.855079+0800 GCD_Demo[1392:110169] task2 end
2019-01-13 12:22:32.855434+0800 GCD_Demo[1392:110169] task3

然后我们dispatch_barrier_async换成dispatch_barrier_sync:

- (void)dispatch_barrier_sync_demo {
    dispatch_queue_t queue = dispatch_queue_create("gcdtest.rongfzh.yc", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"dispatch_async1");
    });
    dispatch_barrier_async(queue, ^{
        NSLog(@"dispatch_barrier_async_start");
        [NSThread sleepForTimeInterval:4];
        NSLog(@"dispatch_barrier_async_end");
        
    });
    dispatch_async(queue, ^{
        NSLog(@"dispatch_async3");
    });
}
控制台打印:
2019-01-13 12:23:32.132838+0800 GCD_Demo[1407:112641] task1
2019-01-13 12:23:32.132959+0800 GCD_Demo[1407:112605] task2 start
2019-01-13 12:23:36.134056+0800 GCD_Demo[1407:112605] task2 end
2019-01-13 12:23:36.134298+0800 GCD_Demo[1407:112605] after diapatch barrier
2019-01-13 12:23:36.134507+0800 GCD_Demo[1407:112641] task3

说明:我们在task2中用一个延时操作来模拟耗时的任务,两个demo的区别就是after diapatch barrier的打印时机。通过dispatch_barrier_async方式,after diapatch barrier不用等到task2任务执行完就可以打印;通过dispatch_barrier_async方式,after diapatch barrier要等到task2任务执行完才可以打印。

dispatch_barrier_async的妙用:实现一个多读单写的多线程锁。

多读单写的锁特点:

  • 在进行写操作的同时,不能同时有其他写操作或者读操作
  • 允许多个读操作同时进行

具体的实现方式如下:

#import "TestClass.h"

@interface TestClass (){
    NSString *_testName;
}

@property (nonatomic, copy) NSString *testName;

@property (nonatomic, strong) dispatch_queue_t rw_queue;

@end


@implementation TestClass

- (instancetype)init
{
    self = [super init];
    if (self) {
        _rw_queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);
    }
    return self;
}

- (void)setTestName:(NSString *)testName {
    dispatch_barrier_async(_rw_queue, ^{
        _testName = [testName copy];
    });
}

- (NSString *)testName {
    __block NSString *temp;
    dispatch_sync(_rw_queue, ^{
        temp = _testName;
    });
    return temp;
}

@end
dispatch_once

dispatch_once用来执行只需要执行一次的任务,常常用来设计单利模式。

用法:
- (void)once
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //需要单次执行的任务
    });
}
调度组dispatch_group_t

我们在多线程开发中,经常会遇到在多个任务执行结束后再进行一些总结性的任务操作。这是GCD调度组就可以派上用场了。

// 实例化一个调度组
dispatch_group_t group = dispatch_group_create();
// 队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 任务添加到队列queue
dispatch_group_async(group, queue, ^{
    [NSThread sleepForTimeInterval:4];
    NSLog(@"耗时操作1");
});
dispatch_group_async(group, queue, ^{
    [NSThread sleepForTimeInterval:2];
    NSLog(@"耗时操作2");
});
dispatch_group_async(group, queue, ^{
    [NSThread sleepForTimeInterval:3];
    NSLog(@"耗时操作3");
});
dispatch_group_notify(group, queue, ^{
    NSLog(@"所有操作都执行完了"); // 异步的
});
控制台打印:
2019-01-13 14:17:27.840243+0800 GCD_Demo[2498:289933] 耗时操作2
2019-01-13 14:17:28.840083+0800 GCD_Demo[2498:289932] 耗时操作3
2019-01-13 14:17:29.840024+0800 GCD_Demo[2498:289929] 耗时操作1
2019-01-13 14:17:29.840398+0800 GCD_Demo[2498:289929] 所有操作都执行完了
延时操作dispatch_after

经过某个时间后执行某个任务

dispatch_after(dispatch_time_t when, dispatch_queue_t queue,
        dispatch_block_t block);        
        

相关文章

网友评论

      本文标题:iOS多线程之:GCD

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