1.为什么用GCD

GCD 是苹果公司为多核的并行运算提出的解决方案。

  • 它会主动利用更多的CPU内核(比方双核、四核),
  • 会主动办理线程的生命周期(创立线程、调度使命、销毁线程)

程序员只需求告诉 GCD 想要履行什么使命,不需求编写任何线程办理代码。

2.使命(同步异步) & 行列(串行并发)

使命:便是你在线程中履行的那段代码。履行使命有两种办法:『同步履行』『异步履行』。两者的首要区别是:是否等候行列的使命履行结束,以及是否具有敞开新线程的才能。

  • 同步 在当时线程中履行使命,使命未履行完时,会堵塞线程,不会拓荒线程。运用办法如下
dispatch_sync(queue, ^{
   // do something
});
  • 异步 能够在新的线程中履行使命,具有敞开新线程的才能。运用办法如下
dispatch_async(queue, ^{
   // do something
});

GCD 中有两种行列:『串行行列』『并发行列』,两者都契合 FIFO(先进先出)的原则。

  • 串行 只敞开一个线程,一个使命履行结束后,再履行下一个使命。
  • 并发 能够敞开多个线程,而且一起履行使命。
/*
  第一个参数表明行列的唯一标识符,用于 DEBUG,可为空。
行列的称号推荐运用应用程序 ID 这种逆序全程域名。
  第二个参数用来识别是串行行列仍是并发行列。
`DISPATCH_QUEUE_SERIAL` 表明串行行列,`DISPATCH_QUEUE_CONCURRENT` 表明并发行列。
*/
dispatch_queue_t queue = dispatch_queue_create("com.test.company", DISPATCH_QUEUE_SERIAL);

注意:并发行列的并发功用只要在异步(dispatch_async)办法下才有用。

3.组合运用

同步串行

使命一个接一个履行,不拓荒线程

/**
 串行行列,同步履行
 */
- (void)createSerialQueueWithSync {
    dispatch_queue_t serialQueue = dispatch_queue_create("com.neusoft", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(serialQueue, ^{
        NSLog(@"A==%@", [NSThread currentThread]);
    dispatch_sync(serialQueue, ^{
        NSLog(@"B==%@", [NSThread currentThread]);
    });
    dispatch_sync(serialQueue, ^{
        NSLog(@"C==%@", [NSThread currentThread]);
    });
    NSLog(@"D==%@", [NSThread currentThread]);
}

成果如下

iOS 多线程(二):GCD

同步并发

使命一个接一个履行,不拓荒线程

/**
 并行行列,同步履行
 */
- (void)createConcurrentQueueWithSync {
    dispatch_queue_t conCurrentQueue = dispatch_queue_create("com.neusoft", DISPATCH_QUEUE_CONCURRENT);
    //    dispatch_queue_t conCurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);//这个是全局并行行列,一般并行使命都会加到这里边去
    dispatch_sync(conCurrentQueue, ^{
        NSLog(@"A==%@", [NSThread currentThread]);
    });
    dispatch_sync(conCurrentQueue, ^{
        NSLog(@"B==%@", [NSThread currentThread]);
    });
    dispatch_sync(conCurrentQueue, ^{
        NSLog(@"C==%@", [NSThread currentThread]);
    });
    NSLog(@"D==%@", [NSThread currentThread]);
}

打印成果如下

iOS 多线程(二):GCD

异步串行

使命一个接一个履行,会拓荒线程。

/**
 串行行列,异步履行
 */
- (void)createSerialQueueWithAsync {
    dispatch_queue_t serialQueue = dispatch_queue_create("com.neusoft", DISPATCH_QUEUE_SERIAL);
    dispatch_async(serialQueue, ^{
        NSLog(@"A==%@", [NSThread currentThread]);
    });
    dispatch_async(serialQueue, ^{
        NSLog(@"B==%@", [NSThread currentThread]);
    });
    dispatch_async(serialQueue, ^{
        NSLog(@"C==%@", [NSThread currentThread]);
    });
    NSLog(@"D==%@", [NSThread currentThread]);
}

打印成果如下

iOS 多线程(二):GCD

异步并发

使命乱序履行,拓荒线程。

/**
 并行行列,异步履行
 */
- (void)createConcurrentQueueWithAsync {
    dispatch_queue_t conCurrentQueue = dispatch_queue_create("com.neusoft", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(conCurrentQueue, ^{
        NSLog(@"A==%@", [NSThread currentThread]);
    });
    dispatch_async(conCurrentQueue, ^{
        NSLog(@"B==%@", [NSThread currentThread]);
    });
    dispatch_async(conCurrentQueue, ^{
        NSLog(@"C==%@", [NSThread currentThread]);
    });
    NSLog(@"D==%@", [NSThread currentThread]);
}

打印成果如下

iOS 多线程(二):GCD

4.GCD其他用法

dispatch_barrier

咱们有时需求履行两组操作,且要求第一组(使命1、2、3)操作履行完之后,才能开端履行第二组(使命4、5、6)操作。这时就需求用到栅门函数,栅门函数的效果如下图所示

iOS 多线程(二):GCD

从图中咱们能够看出

  • 对于串行行列来说,使命是一个接一个履行的,所以对串行行列运用栅门函数含义不大
  • 对于并发行列来说,使命是乱序履行的,所以运用栅门函数能够很好的操控行列内使命履行的次序

运用

举例如下

- (void)test {
    dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"开端——%@", [NSThread currentThread]);
    dispatch_async(queue, ^{
        sleep(2);
        NSLog(@"推迟2s的使命1——%@", [NSThread currentThread]);
    });
    NSLog(@"第一次结束——%@", [NSThread currentThread]);
//    dispatch_barrier_async(queue, ^{
//        NSLog(@"----------栅门使命----------%@", [NSThread currentThread]);
//    });
//    NSLog(@"栅门结束——%@", [NSThread currentThread]);
    dispatch_async(queue, ^{
        sleep(1);
        NSLog(@"推迟1s的使命2——%@", [NSThread currentThread]);
    });
    NSLog(@"第2次结束——%@", [NSThread currentThread]);
}

不运用栅门函数

开端——<NSThread: 0x600002384f00>{number = 1, name = main}
第一次结束——<NSThread: 0x600002384f00>{number = 1, name = main}
第2次结束——<NSThread: 0x600002384f00>{number = 1, name = main}
推迟1s的使命2——<NSThread: 0x6000023ec300>{number = 5, name = (null)}
推迟2s的使命1——<NSThread: 0x60000238c180>{number = 7, name = (null)}

运用栅门函数

开端——<NSThread: 0x600000820bc0>{number = 1, name = main}
第一次结束——<NSThread: 0x600000820bc0>{number = 1, name = main}
栅门结束——<NSThread: 0x600000820bc0>{number = 1, name = main}
第2次结束——<NSThread: 0x600000820bc0>{number = 1, name = main}
推迟2s的使命1——<NSThread: 0x600000863c80>{number = 4, name = (null)}
----------栅门使命----------<NSThread: 0x600000863c80>{number = 4, name = (null)}
推迟1s的使命2——<NSThread: 0x600000863c80>{number = 4, name = (null)}

总结一句话便是: 先履行栅门前使命,再履行栅门使命,最终履行栅门后使命

问题

1、dispatch_barrier_asyncdispatch_barrier_sync有什么区别,该用那个?

两者效果相同,都是先履行栅门前使命,再履行栅门使命,最终履行栅门后使命。但是dispatch_barrier_sync会堵塞线程,影响后面的使命履行(尽量少用)。

比方将上例的dispatch_barrier_async改成dispatch_barrier_sync,成果如下

开端——<NSThread: 0x600001040d40>{number = 1, name = main}
第一次结束——<NSThread: 0x600001040d40>{number = 1, name = main}
推迟2s的使命1——<NSThread: 0x60000100ce40>{number = 6, name = (null)}
----------栅门使命----------<NSThread: 0x600001040d40>{number = 1, name = main}
栅门结束——<NSThread: 0x600001040d40>{number = 1, name = main}
第2次结束——<NSThread: 0x600001040d40>{number = 1, name = main}
推迟1s的使命2——<NSThread: 0x60000100ce40>{number = 6, name = (null)}

注意:栅门函数有必要是自定义的并发行列才有用,且有必要是同一行列中的线程才有用。

读写锁

读写锁在读多写少的场景效率很高,它的定义是:同一时刻有多个读者,同一时刻只能有一个写者,读者和写者不能一起存在

工作原理:读写锁实际是一种特殊的自旋锁,假如读写锁当时没有读者,也没有写者,那么写者能够马上获得读写锁,否则它有必要自旋在那里, 直到没有任何写者或读者。

写的那个时刻段,不能有任何读者+其他写者,因而依据栅门函数原理,咱们能够把读写操作了解成下图

iOS 多线程(二):GCD

- (id)readDataForKey:(NSString*)key {
    __block id value;
    dispatch_sync(_concurrentQueue, ^{
        value = [self valueForKey:key];
    });
    return value;
}
- (void)writeData:(id)data forKey:(NSString*)key {
    dispatch_barrier_async(_concurrentQueue, ^{
        [self setValue:data forKey:key];
    });
}

为什么用dispatch_sync完结读,用dispatch_barrier_async完结写?

  • 读:运用dispatch_sync,会使 value 获取到值后,再回来给读者
    • 若运用dispatch_async则会先回来空的result value,再经过[self valueForKey:key]办法获取到值
  • 写:写的那个时刻段,不能有任何读者+其他写者,dispatch_barrier_async满足等行列中前面的读写使命都履行完了再来履行当时使命,而不堵塞当时线程

dispatch_group

调度组有以下才能

  1. 将使命分组履行dispatch_group_enter(group), dispatch_group_leave(group)
  2. 能监听使命组完结dispatch_notify(group, queue, ^{});
  3. 并设置等候时刻dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

dispatch_group_async

- (void)createGroupQueue {
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_async(group, queue, ^{
        NSLog(@"A---%@", [NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"B---%@", [NSThread currentThread]);
        }
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"C---%@", [NSThread currentThread]);
    });
    dispatch_notify(group, queue, ^{
        NSLog(@"行列组使命履行结束");
    });
}

dispatch_group_enter & dispatch_group_leave

- (void)test2 {
    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, ^{
        NSLog(@"A---%@", [NSThread currentThread]);
        dispatch_group_leave(group);
    });
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"B---%@", [NSThread currentThread]);
        }
        dispatch_group_leave(group);
    });
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"C---%@", [NSThread currentThread]);
        dispatch_group_leave(group);
    });
    dispatch_notify(group, queue, ^{
        NSLog(@"行列组使命履行结束");
    });
}

dispatch_group_wait

dispatch_group_wait能够设置调度组的等候时刻,这也意味着会堵塞当时线程,等候调度组完结使命,若在指定时刻内完结使命,会回来 0;否则回来非 0。

语法:long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout)

  • group:需求等候的调度组
  • timeout:等候的超时时刻(即等多久)
    • 设置为DISPATCH_TIME_NOW意味着不等候直接判定调度组是否履行结束
    • 设置为DISPATCH_TIME_FOREVER则会堵塞当时调度组,直到调度组履行结束
    • 设置具体超时时刻dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC))
  • 回来值:为long类型
    • 0 表明在指定时刻内调度组完结了使命
    • 非0 表明指定时刻内调度组没有准时完结使命

举例如下

- (void)test {
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"恳求一完结");
        dispatch_group_leave(group);
    });
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"恳求二完结");
        dispatch_group_leave(group);
    });
//    long timeout = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    // 设置 1 秒超时,在这 1 秒期间会堵塞当时线程
    long timeout = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC));
    NSLog(@"timeout=%ld", timeout);
    if (timeout == 0) {
        NSLog(@"准时完结使命");
    } else {
        NSLog(@"超时");
    }
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"告诉,所有恳求完结");
    });
}

成果如下

timeout=49 // 49 便是非 0,即超时回来过错
超时
恳求一完结
恳求二完结
告诉,所有恳求完结

dispatch_semaphore

信号量可用于操控 GCD 最大并发数,还能够当锁运用(信号量锁的效率也很高)。

  • dispatch_semaphore_create(intptr_t value);:创立信号量
  • dispatch_semaphore_wait(semaphore, dispatch_time_t timeout):等候信号量,信号量-1。当信号量< 0时会堵塞当时线程,依据传入的等候时刻决议接下来的操作(假如永久等候将比及信号(signal)才履行下去)
  • dispatch_semaphore_signal(semaphore);:释放信号量,信号量+1。当信号量>= 0 会履行wait之后的代码

比方AFURLSessionManager.m中的tasksForKeyPath:办法。经过引进信号量的办法,等候异步履行使命成果,获取到 tasks,然后再回来该 tasks。

- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
    __block NSArray *tasks = nil;
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
        if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
            tasks = dataTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
            tasks = uploadTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
            tasks = downloadTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
            tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
        }
        dispatch_semaphore_signal(semaphore);
    }];
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    return tasks;
}

dispatch_once

咱们在创立单例、或许有整个程序运转过程中只履行一次的代码时,咱们就用到了 GCD 的 dispatch_once 办法。运用 dispatch_once 办法能确保某段代码在程序运转过程中只被履行 1 次,而且即使在多线程的环境下,dispatch_once 也能够确保线程安全。

/**
 * 一次性代码(只履行一次)dispatch_once
 */
- (void)once {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 只履行 1 次的代码(这里边默认是线程安全的)
    });
}

有时dispatch_once可用作优化,类似程序运转期间要屡次获取的值,能够只履行一次耗时操作,然后把值存起来供下次直接运用,比方下例中获取设备等级的代码

+ (NSInteger)getDeviceLevel {
    static dispatch_once_t deviceLevelOnce;
    static NSInteger deviceLevel;
    dispatch_once(&deviceLevelOnce, ^{
        struct utsname systemInfo;
        uname(&systemInfo);
        NSString *deviceModel = [NSString stringWithCString:systemInfo.machine encoding:NSASCIIStringEncoding];
        NSBundle *bundle = [NSBundle c4_localizedBundle];
        NSString *path = [bundle pathForResource:@"Apple_mobile_device_types" ofType:@"plist"];
        NSDictionary *deviceDict = [NSDictionary dictionaryWithContentsOfFile:path];
        NSDictionary *dict = deviceDict[deviceModel];
        NSString *level = dict[@"level"];
        if (!level) {
            deviceLevel = 4;
        } else {
            deviceLevel = level.integerValue;
        }
    });
    return deviceLevel;
}

dispatch_apply

//快速迭代办法
dispatch_apply(size_t iterations,
               dispatch_queue_t DISPATCH_APPLY_QUEUE_ARG_NULLABILITY queue,
               DISPATCH_NOESCAPE void (^block)(size_t iteration));

当 queue 是串行行列时,它的效果和 for 循环相同,所以一般用于并行行列。

运用办法如下

- (void)testApply {
  NSLog(@"dispatch_apply 开端履行");
  dispatch_apply(6, dispatch_get_global_queue(0, 0), ^(size_t iteration) {
    NSLog(@"%zd---%@", iteration, [NSThread currentThread]);
  });
  NSLog(@"dispatch_apply 结束履行");
}

成果如下

dispatch_apply 开端履行
1---<NSThread: 0x600003340d40>{number = 9, name = (null)}
2---<NSThread: 0x600003334000>{number = 11, name = (null)}
3---<_NSMainThread: 0x600003328080>{number = 1, name = main}
4---<NSThread: 0x60000331a4c0>{number = 12, name = (null)}
0---<NSThread: 0x60000331a3c0>{number = 10, name = (null)}
5---<NSThread: 0x600003348f40>{number = 13, name = (null)}
dispatch_apply 结束履行

无论是在串行行列,仍是并发行列中,dispatch_apply 都会等候全部使命履行结束,这点就像是同步操作,也像是行列组中的dispatch_group_wait办法。

dispatch_source

常用应用场景是 GCD Timer。

一般运用NSTimer来处理定时逻辑,但NSTimer是依赖 Runloop 的,而 Runloop 能够运转在不同的形式下。假如NSTimer添加在一种形式下,当 Runloop 运转在其他形式下的时候,定时器就挂机了;又假如 Runloop 在堵塞状态,NSTimer触发时刻就会推迟到下一个 Runloop 周期。因而NSTimer在计时上会有误差,并不是特别准确,而 GCD 定时器不依赖 Runloop ,计时精度要高很多

常用 API

  • dispatch_source_create: 创立事情源
  • dispatch_source_set_event_handler: 设置数据源回调
  • dispatch_source_merge_data: 设置事情源数据
  • dispatch_source_get_data: 获取事情源数据
  • dispatch_resume: 继续
  • dispatch_suspend: 挂起
  • dispatch_cancle: 撤销

创立 GCD Timer 例子,摘自 YYTimer

@property (nonatomic, strong) dispatch_source_t timer;
//创立timer
_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
//设置timer首次履行时刻,距离,准确度
dispatch_source_set_timer(_source, dispatch_time(DISPATCH_TIME_NOW, (start * NSEC_PER_SEC)), (interval * NSEC_PER_SEC), 0);
//设置timer事情回调
__weak typeof(self) _self = self;
dispatch_source_set_event_handler(_source, ^{
    [_self fire];
});
//默认是挂起状态,需求手动激活
dispatch_resume(_source);