iOS开发-GCD技术

前言

对于OS XiOS的XNU内核线程切换时,会进行上下文切换,例如CPU的寄存器等信息保存到各自路径专用的内存块中,从切换目标路径专用的内存块中,复原CPU寄存器等信息,继续执行切换路径的CPU命令行。这被称为“上下文切换”。

使用多线程的程序可以在某个线程和其他线程之间反复多次进行上下文切换,因此看上去就好像1个CPU核能够并列的执行多个线程一样。而且在具有多个CPU核的情况下,就不是“看上去像”了,而是真的提供了多个CPU核并行执行多个线程的技术。

多线程编程易发生各种问题,例如多个线程对资源的竞争,多个线程之间相互等待造成死锁,以及线程开辟消耗大量的内存等问题。

在这里插入图片描述

多线程的优点在于能够将复杂的处理独立出来,不会阻塞主线程这种线程的执行

在这里插入图片描述

GCD是基于XNU内核的线程技术,大大简化了多线程编程的源码。

Dispatch Queue

一般来说我们会这样异步执行一个任务

dispatch_async(queue,^{
	//do your task
});

queue代表一个队列,在GCD中存在两种队列形式

  1. Serial Dispatch Queue 串行队列
  2. Concurrent Dispatch Queue 并行队列

两种队列的在于,Serial处理task是根据队列先进先出顺序执行,会保证上一个task执行完再执行下一个,而Concurrent则是并发执行,不会等上一个task执行完

在这里插入图片描述

虽然Concurrent Dispatch Queue不用等待处理结束,可以并发执行多个task,但是其并发线程的数量却是取决于当前系统的状态。根据如下:

  1. iOS和OS X基于Dispatch Queue中的处理数
  2. CPU核数以及CPU负荷等当前系统的状态

来决定Concurrent Dispatch Queue中线程数量。

例如:

dispatch_asyc(queue,blk0) 
//.. 1 ~ 6
dispatch_asyc(queue,blk7)

8task可能根据当前系统的状态,会如下执行:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5FZhG2C5-1588154513693)(/Users/gensee/Library/Application Support/typora-user-images/image-20200426130436199.png)]

Queue Create

我们可以通过GCD的API来生成Dispatch Queue

dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.example.gcd.MySerialDispatchQueue",DISPATCH_QUEUE_SERIAL)

值得注意的是Serial Dispatch Queue的生成,当创建一个Serial Dispatch Queue,系统就默认只生成并使用一个线程。如果生成了2000个Serial Dispatch Queue那么就创建2000个线程,这对内存资源消耗是很大的。而Concurrent Dispatch Queue就没这个问题,他会根据系统状态来管理并发线程数量,所以虽然我们可以使用创建多个Serial Dispatch Queue来达到并发的目的,但是并不是GCD的初衷,我们应该使用Concurrent Dispatch Queue来处理并发任务

同样我们可以创建 Concurrent Dispatch Queue

dispatch_queue_t myConcurrentDispatchQueue = dispatch_queue_create("com.example.gcd.MyConcurrentDispatchQueue",DISPATCH_QUEUE_CONCURRENT)

即第二个参数决定了Queue的类型是 Serial 还是 Concurrent

Queue Release

MRC

对于Dispatch QueueQueue必须创建后由程序员自己释放,因为Dispatch Queue并没有像block那样具有作为Object-C对象来处理的技术。

dispatch_release(myConcurrentDispatchQueue)

但是,Dispatch Queue同样有引用计数管理。意味着你可以

dispatch_retain(myConcurrentDispatchQueue)

例如下面代码

    dispatch_queue_t myConcurrentDispatchQueue = dispatch_queue_create("com.example.gcd.MyConcurrentDispatchQueue",DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(myConcurrentDispatchQueue, ^{
        //do your task
    });
    dispatch_release(myConcurrentDispatchQueue);

既然dispatch_release释放掉了,为什么任务还能执行,这也是因为blockqueue进行了引用,就相当于调用了dispatch_retain

ARC

对于ARC模式下,你不需要手动调用dispatch_release(myConcurrentDispatchQueue);,ARC已经允许将Dispatch Queue作为对象加入ARC内存管理。

Global Queue

我们可以获取系统为我们提供的标准Global Queue

iOS 7之前,系统提供了4种优先级,以调度优先级区分

名称执行优先级
DISPATCH_QUEUE_PRIORITY_HIGH高(最高优先)
DISPATCH_QUEUE_PRIORITY_DEFAULT默认
DISPATCH_QUEUE_PRIORITY_LOW
DISPATCH_QUEUE_PRIORITY_BACKGROUND后台

iOS 8之后,系统提供了5种优先级,以服务质量划分的

名称执行优先级
QOS_CLASS_USER_INTERATCTIVE高(最高优先)
QOS_CLASS_USER_INITIATED用户交互(不要使用耗时操作)
QOS_CLASS_DEFAULT默认
QOS_CLASS_UTILITY使用工具(用了做耗时操作)
QOS_CLASS_BACKGROUND后台执行
QOS_CLASS_UNSPECIFIED没有指定优先级

对于QOS_CLASS_UTILITY我们一般进行耗时操作,例如数据I/O,系统对其进行了优化,使得能够减少App的电量消耗。

如何设置QOS级别

dipatch_queue_attr_make_with_qos_classdispatch_set_target_queue方法设置队列的优先级

//dipatch_queue_attr_make_with_qos_class

dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, -1);

dispatch_queue_t queue = dispatch_queue_create("com.starming.gcddemo.qosqueue", attr);



//dispatch_set_target_queue

dispatch_queue_t queue = dispatch_queue_create("com.starming.gcddemo.settargetqueue",NULL); //需要设置优先级的queue

dispatch_queue_t referQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0); //参考优先级

dispatch_set_target_queue(queue, referQueue); //设置queue和referQueue的优先级一样

Dispatch Set Target

void dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t _Nullable queue);

dispatch_set_target_queue能够设置queue的执行阶层,无论是Concurrent Queue还是Serial Queue,一旦设置target为Serial Queue就是串行执行。

例如下列代码:

- (void)set_tag_test {
    dispatch_queue_t serialQueue1 = dispatch_queue_create("com.gcd.setTargetQueue2.serialQueue1", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t serialQueue2 = dispatch_queue_create("com.gcd.setTargetQueue2.serialQueue2", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t serialQueue3 = dispatch_queue_create("com.gcd.setTargetQueue2.serialQueue3", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t serialQueue4 = dispatch_queue_create("com.gcd.setTargetQueue2.serialQueue4", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t serialQueue5 = dispatch_queue_create("com.gcd.setTargetQueue2.serialQueue5", DISPATCH_QUEUE_CONCURRENT);
    
    
    
    //创建目标串行队列
    dispatch_queue_t targetSerialQueue = dispatch_queue_create("com.gcd.setTargetQueue2.targetSerialQueue", NULL);
    
    //设置执行阶层
    dispatch_set_target_queue(serialQueue1, targetSerialQueue);
    dispatch_set_target_queue(serialQueue2, targetSerialQueue);
    dispatch_set_target_queue(serialQueue3, targetSerialQueue);
    dispatch_set_target_queue(serialQueue4, targetSerialQueue);
    dispatch_set_target_queue(serialQueue5, targetSerialQueue);
    
    dispatch_async(serialQueue1, ^{
        NSLog(@"%@,1",[NSThread currentThread]);
    });
    dispatch_async(serialQueue1, ^{
        NSLog(@"%@,1 2",[NSThread currentThread]);
    });
    dispatch_async(serialQueue2, ^{
        NSLog(@"%@,2",[NSThread currentThread]);
    });
    dispatch_async(serialQueue3, ^{
        NSLog(@"%@,3",[NSThread currentThread]);
    });
    dispatch_async(serialQueue4, ^{
        NSLog(@"%@,4",[NSThread currentThread]);
    });
    dispatch_async(serialQueue5, ^{
        NSLog(@"%@,5",[NSThread currentThread]);
    });
    dispatch_async(targetSerialQueue, ^{
        NSLog(@"%@,6",[NSThread currentThread]);
    });
}

输出为:

<NSThread: 0x60000239b580>{number = 3, name = (null)},1
<NSThread: 0x60000239b580>{number = 3, name = (null)},1 2
<NSThread: 0x60000239b580>{number = 3, name = (null)},2
<NSThread: 0x60000239b580>{number = 3, name = (null)},3
<NSThread: 0x60000239b580>{number = 3, name = (null)},4
<NSThread: 0x60000239b580>{number = 3, name = (null)},5
<NSThread: 0x60000239b580>{number = 3, name = (null)},6

dispatch_after

  1. 延迟执行
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
    dispatch_after(time, dispatch_get_main_queue(),^{
        //do task
    });

dispatch_after是延迟3s加入Dispatch Queue,并不是马上执行,例如加入到主线程时,如果主线程卡段,这个延迟会大于3s

  1. 某一绝对时间执行

如果dispatch_after需要在2020/04/27 11:11:11执行,那么dispatch_time_t就可以使用dispatch_walltime构造

dispatch_time_t dispatch_walltime(const struct timespec *_Nullable when, int64_t delta);
    NSTimeInterval interval = [[NSDate date] timeIntervalSince1970];
    struct timespec time;
    dispatch_time_t milestone;
    double second,subsecond;
    subsecond = modf(interval, &second);
    time.tv_sec = second;
    time.tv_nsec = subsecond * NSEC_PER_SEC;
    milestone = dispatch_walltime(&time, 0);

GCD timer

void
dispatch_source_set_timer(dispatch_source_t source, //source timer
	dispatch_time_t start, //开始时间
	uint64_t interval, //执行间隔
	uint64_t leeway); //允许的延迟时间 - 系统会尽量满足,但不一定保证在延迟时间内执行

一般来说我们定时器这样写:

    timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
    dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, (80 * NSEC_PER_MSEC)), (80 * NSEC_PER_MSEC), 0);
    dispatch_source_set_event_handler(timer, ^{
        //
    });
    dispatch_resume(timer);

注意dispatch_source是遵循ARC的,要强引用保证其不被释放。

  • 为什么GCD定时器相比NSTimer定时器更准?

NSTimerCFRunLoopTimerReftoll-free bridged的,可以直接转换,当某一次执行被延后,错过了时间点,那么NSTimer会选择不执行此次回调,等到下次执行。所以相比来说,GCD定时器更为准确。

此外 GCD定时器无法保证每次的执行同时在一个线程中,就算是Serial队列也不行,而NSTimer是加入到线程的runloop中,所以必定是同一个线程,当我们需要单线程执行时,需要注意这点。

Dispatch Group

dispatch_group_notify

我们一般用dispatch_group处理多个任务完成后,执行某个操作

- (void)dispatch_group_test {
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk0;");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk1;");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk2;");
    });
    
    //1. notify
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"done");
    });
    
    //MRC
//    dispatch_release(group);
}

控制台输出为:

2020-04-29 16:30:20.748658+0800 GCDTest[51629:2202347] blk0;
2020-04-29 16:30:20.748668+0800 GCDTest[51629:2202345] blk1;
2020-04-29 16:30:20.748687+0800 GCDTest[51629:2202346] blk2;
2020-04-29 16:30:20.761685+0800 GCDTest[51629:2201079] done

追加到Dispatch Groupblock会对Dispatch Group进行dispatch_retain并持有,在block 执行完之后释放Dispatch Group,所以一旦Dispatch Group使用结束,意味着block都不保持其引用了,可以放心释放。

dispatch_group_wait

判断指定时间后,task是否执行完,使用dispatch_group_wait

将上面1部分替换为:

		//2. wait 1 sec
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull*NSEC_PER_SEC);
    long result = dispatch_group_wait(group, time);
    
    if (result == 0) {
        // Group 中task全部执行完
    }else {
        // Group 中task还在执行中
    }
  1. 我们可以通过将time参数设置为DISPATCH_TIME_NOW,然后在Runloop每次循环中来判断task是否执行完,以达到和dispatch_group_notify同样的效果。

  2. 我们可以通过将time参数设置为DISPATCH_TIME_FOREVER来保证任务一定执行完成。等同于dispatch_group_notify

dispatch_barrier_async

对于访问数据库文件,我们要避免数据竞争,资源是互斥的。虽然我们可以使用Serial Dispatch Queue来解决这样的问题,但是为了高效处理,写入操作确实不可与其他写入以及读取操作同时进行,但是多个读取操作时可以同时进行的,只需要保证写入是在多个读取之后进行。

- (void)dispatch_barrier_test {
    dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.barrier", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"read 0");
    });
    dispatch_async(queue, ^{
        NSLog(@"read 1");
    });
    dispatch_async(queue, ^{
        NSLog(@"read 2");
    });
    dispatch_barrier_async(queue, ^{
        NSLog(@"write");
    });
    dispatch_async(queue, ^{
        NSLog(@"read 3");
    });
    dispatch_async(queue, ^{
        NSLog(@"read 4");
    });
    dispatch_async(queue, ^{
        NSLog(@"read 5");
    });
}

这样就可以保证多次并发read

2020-04-29 17:45:00.149982+0800 GCDTest[56002:2263229] read 2
2020-04-29 17:45:00.149982+0800 GCDTest[56002:2263230] read 0
2020-04-29 17:45:00.149999+0800 GCDTest[56002:2263239] read 1
2020-04-29 17:45:00.150130+0800 GCDTest[56002:2263239] write
2020-04-29 17:45:00.150215+0800 GCDTest[56002:2263239] read 3
2020-04-29 17:45:00.150220+0800 GCDTest[56002:2263230] read 4
2020-04-29 17:45:00.150234+0800 GCDTest[56002:2263229] read 5

dispatch_apply

当我们需要将一个block加入到queue中,并多次执行时,使用dispatch_apply,dispatch_apply会阻塞当前线程,并等待queue执行完成。

- (void)dispatch_apply_test {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_apply(10, queue, ^(size_t index) {
        NSLog(@"%zu",index);
    });
    NSLog(@"done");
}

输出为:

2020-04-29 18:00:27.838731+0800 GCDTest[56107:2271775] 0
2020-04-29 18:00:27.838739+0800 GCDTest[56107:2271897] 1
2020-04-29 18:00:27.838739+0800 GCDTest[56107:2271893] 2
2020-04-29 18:00:27.838759+0800 GCDTest[56107:2271894] 3
2020-04-29 18:00:27.838858+0800 GCDTest[56107:2271897] 4
2020-04-29 18:00:27.838860+0800 GCDTest[56107:2271893] 5
2020-04-29 18:00:27.838861+0800 GCDTest[56107:2271894] 6
2020-04-29 18:00:27.838898+0800 GCDTest[56107:2271775] 7
2020-04-29 18:00:27.838941+0800 GCDTest[56107:2271897] 8
2020-04-29 18:00:27.838951+0800 GCDTest[56107:2271893] 9
2020-04-29 18:00:27.839616+0800 GCDTest[56107:2271775] done

dispatch_resume/dispatch_suspend

当多个taskDispatch Queue中执行时,有时不想再继续执行了,可以使用dispatch_suspend将队列挂起。再使用dispatch_resume恢复,值得注意的是,并不能取消已经开始的任务。

对于想取消的线程,并能监听线程的各种状态,可以使用NSOperationNSOperationQueue,还能够设置最大并发量。

NSOperationNSOperationQueue 是苹果提供给我们的一套多线程解决方案。实际上 NSOperationNSOperationQueue 是基于 GCD更高一层的封装,完全面向对象。但是比 GCD 更简单易用、代码可读性也更高。

  1. 可以添加任务依赖,方便控制执行顺序

  2. 可以设定操作执行的优先级

  3. 任务执行状态控制:isReady,isExecuting,isFinished,isCancelled

如果只是重写NSOperationmain方法,由底层控制变更任务执行及完成状态,以及任务退出
如果重写了NSOperationstart方法,自行控制任务状态
系统通过KVO的方式移除isFinished==YESNSOperation

  1. 可以设置最大并发量

参考链接:https://www.jianshu.com/p/cbf759bdfd0f

dispatch TLS

线程TLS技术,Thread Local Storage(TLS)线程局部存储,目的很简单,将一块内存作为某个线程专有的存储,以key-value的形式进行读写,比如在非arm架构下,使用pthread提供的方法实现:

void* pthread_getspecific(pthread_key_t);
int pthread_setspecific(pthread_key_t , const void *);

GCD中也提供了相关的技术

#import <Foundation/Foundation.h>

static const void * const key1 = &key1;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        dispatch_queue_t queue1 = dispatch_queue_create("com.ebebya.queue1", NULL);
        dispatch_queue_set_specific(queue1, key1, (void *)[@"ebebya" UTF8String], NULL);
        
        dispatch_async(queue1, ^{
            void *value = dispatch_get_specific(key1);
            NSString *str = [[NSString alloc] initWithBytes:value length:7 encoding:4];
            NSLog(@"%@", str);
        });
    }
    return 0;
}

但是是对于一个queue队列的上下文键值对,具体实现就不追究了。


demo链接:https://pan.baidu.com/s/1LDMFweqPKBbTFPzY2mhUxg
密码:tfvz

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值