GCD笔记和问题汇总

参考了不少博客,部分参考别人,附含一些自己的见解

参考博客:http://www.tuicool.com/articles/ZNn6Jz

1、GCD是我们iOS开发很重要的一些方法,它形式简单,但功能强大,但里面有很多又难以让人理解

比如进程和线程的区别?串行队列,和并行队列的区别?以及同步和异步的区别?get和post的区别?

dispatch_get_global_queue(0,0)和dispatch_get_main_queue(),还有dispatch_queue_create("test",NUll)这三种方法创建队列的区别?

2、GCD是用c语言写的函数,dispatch_queue_create(参数1,参数2);参数1是队列的名称,在调试时会有用,所以尽量不要用重名,第二个参数,我见过的有DISPATCH_QUEUE_SERIAL,这代表生存的队列是串行的,任务在单线程中执行,加载到此队列里面的任务会按先进先出的方式执行。

如果第二个参数是DISPATCH_QUEUE_CONCURRENT那么生存的队列是一个并发队列,里面的任务被分发到多个线程中去执行。

dispatch_get_global_queue(0,0)获得其实是一个并发队列

dispatch_get_main_queue()获得的其实是一个串行队列

为什么说这连个队列都不能用dispatch_resume()和dispatch_suspend()来控制队列的继续还是中断,解释原因是因为这是系统自动生存的。

3、接下来就是加载任务的同步和异步

dispatch_async(queue,^ {

});//异步执行block,函数立即返回

dispatch_sync(queue,^{

//block具体代码

});//同步执行block,函数一直等到block执行完毕才返回。

同步我们在使用中尽量少用,它是必须让queue执行完毕后才能执行里面的东西

4、在测试的过程中我发现这么一个问题
dispatch_sync(dispatch_get_main_queue(), ^{

          NSLog(@"同步3");

   });//单独写这么一个方法将之方到viewdidload里面执行,发现那句打印语句根本就不执行,但是如果我们将队列换成global或者换成一个新建的队列就执行里面的语句就能执行,我的理解是可能主线程队列还没有结束,必须等到他结束了才执行,而其他队列都不是主线程,相当于在另一个线程中运行。上面那里出现不打印就是死锁,这是我的理解。

4、项目中常见的GCD模型

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^ {

                   //子线程中开始网络请求数据,全局队列执行

                dispatch_sync(dispatch_get_main_queue(),^{

               //主线程更新UI代码

                });

});//注意特征,前一个队列是全局队列,也是一个子线程,或者说后台执行;后一个队列是主线程

5、可以用串行队列实现一个锁的功能,比如多线程写同一个数据库,需要保持写入的顺序和每一个写入的完整性

dispatch_queue_t queue = dispatch_queue_create("com.dispatch.writedb",DISPATCH_QUEUE_SERIAL);//这是一个串行队列

- (void)writeDB:(NSData *data) {

         dispatch_sync(queue,^{

)};//同步必须等queue执行完毕再说

6、一些特别的dispatch方法

一次性执行

static dispatch_once_t onceToken;

dispatch_once(&onceToken,^{

    //代码执行的地方

});//这个在我们建单例的时候用到它。

延时执行

double delayInSeconds = 2.0;

dispatch_time_t poptime = dispatch_time (DISPATCH_TIME_NOW,delayInSeconds *NSEC_PER_SEC);

dispatch_after(portime,dispatch_get_main_queue(),^(void) {

//执行代码的地方

});

高级用法:让后台连个线程并行执行,然后等两个线程都结束后,再汇总执行结果,代码如下

dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group,dispatch_get_global_queue(0,0),^{

     //并行执行的线程一

});

dispatch_group_async(group,dispatch_get_global_queue(0,0),^{

   //并行执行的线程二 

});

dispatch_group_notify(group,dispatch_get_global_queue(0,0),^{

   //合并结果

});//这个可以用在加载大型图片,太偏太大,如果我们仅仅开一个线程速度可能会慢,我们可以跟后台协商,将一张图片划分为几张图片,然后,分几个线程去下载,然后下载完成后,在合并图片(就是将一部分准确的放到屏幕上去)

(二)第二次学习

(1)、pthread_mutex_lock()和pthread_mutex_unlock()返回0,否则返回一个错误的提示码

pthread_mutex_trylock()在成功获得了一个mutex的锁后返回,否则返回一个错误提示码。

(2)、互斥锁用来保证一段时间内只有一个线程在执行一段代码。必要性显而易见:假设各个线程向同一个文件顺序写入数据,最后得到的结果一定是灾难性的。

下面是Mr peak博客阅读笔记

原文链接:

https://www.jianshu.com/p/4b0dbbd7397a

3、一门语言总是不可能一成不变的,C++也是如此,随着时间的推移它也会有升级变化的改进需求。但是C++这门语言却不像Swift那样不负责任,它的标准和规范的升级相对来说比较严谨。个人觉得原因是其本身已经非常庞大而且完善了,能升级的基本都是微小的调整了。也许你会发现其他很多语言都是C++这门语言的裁剪版。所以可以说学好C++,走遍天下都不怕!

4、另一方面的原因是老设备更新换代太慢,iOS 设备的耐用度极好,现在还有不少 iPhone 4S 在服役,iPhone 6 作为问题设备持有量很高,据估计,现在 iPhone 6s 以前的设备占有比高达 40%

、向 Apple 寻求 technical support 是非常宝贵而且可行的方案,每个开发者账号每年都有 2 次机会,不用非常可惜

5、也就是说卡顿出现在 dispatch_async,以我现有对于 GCD 的认知,dispatch_async 是绝无可能出现卡顿的。dispatch_async 的主要任务是从系统线程池里取出一个工作线程,并将 block 放到该线程里去执行。

6、作者认为,GCD 申请到的线程有可能是一个正在处理其他任务的 thread,main thread 需要等待这个忙碌的线程返回才能继续执行,我对这种说法存疑。

7、最后求助无门的状况下,我决定使用一次宝贵的 TSL 机会,直接向 Apple 的工程师求教。这里不得不提下,向 Apple 寻求 technical support 是非常宝贵而且可行的方案,每个开发者账号每年都有 2 次机会,不用非常可惜,我把问题抛过去后,得到一位 Apple 内核团队工程师的回复,我将精简过的回复以问答的形式展示和大家分享:

8、Q: I know it's suggested that we create limited amount of serial queue,and use target queue probably. but what could happen if we don't follow that rule?

Apple 一直推荐自己创建 serial GCD queue 的时候,一定要控制数量,而且最好设置 target queue,否则会出现问题,但会出现什么问题我一直很好奇,这次借着机会一起问

9、信息一:

iOS 系统本身是一个资源调度和分配系统,CPU,disk IO,VM 等都是稀缺资源,各个资源之间会互相影响,主线程的卡顿看似 CPU 资源出现瓶颈,但也有可能内核忙于调度其他资源,比如当前正在发生大量的磁盘读写,或者大量的内存申请和清理,都会导致下面这个简单的创建线程的内核调用出现卡顿:

libsystem_kernel.dylib __workq_kernreturn

所以解决办法只能是自己分析各 thread 的 call stack,根据用户场景分析当前正在消耗的系统资源。后面也确实通过最近提交的代码分析,发现是由于增加了一些非常耗时的磁盘 io 任务(虽然也是放在在子线程),才出现这个看着不怎么沾边的 call stack。revert 之后卡顿警报就消失了。

10、信息二:

现有的卡顿检测工具都只能在超时的情况下 dump call stack,但出现超时有可能是任务 A,B,C 共同作用导致的,A 和 B 可能是真正耗时的任务,C 不耗时但碰巧是最后一个,所以被当成元凶,而 A 和 B 却没有出现在上报日志里。我暂时也没有想到特别好的解决办法。很明显,libsystem_kernel.dylib __workq_kernreturn 就是一个不怎么耗时的 C 任务。

11、信息三:

在使用 GCD 创建 queue,或者说一个 App 内部使用 GCD 执行子线程任务时,最好有一套 App 所有团队都能遵循的队列使用机制,避免创建过多的 thread,而出现意料之外的线程资源紧缺,代码无法及时执行的情况。这很难,尤其是在大公司动则上百人的团队里面。

(三)自动释放池学习

https://www.jianshu.com/p/554c9fe0f041

1、如果你想要 Cocoa 在 ApplicationKit 的主线程之外调用,比如你创建了一个 Foundation 的 应用或者你创建了一个线程,你需要创建你自己的自动释放池。关键词:主线程外要调用。

2、如果应用或线程是长久保存的并且潜在的生成了很多自动释放的对象,这时应该定期的清空并且创建自动释放池(就像 Application Kit 在主线程中做的那样);否则,对象的积累会增加内存的占用。如果,独立的线程并没有使用 Cocoa 的调用,你没有必要去创建一个自动释放池。关键词:对象的积累会增加内存的占用。

3、如果使用了 POSIX 线程 APIS 而不是 NSThread 对象来创建线程,你不能使用 Cocoa,包括 NSautoreleasePool,除非 Cocoa 是在多线程模式下,Cocoa 进入了多线程模式只有在首次创建 NSThread 对象的时候,为了在第二个 POSIX 线程中使用 Cocoa ,你的应用必须首先至少创建了一个独立的 NSThread 对象,这个对象可以立即退出。你可以通过 NSThread 类方法 isMultiTheraded 来测试 Cocoa 是否在多线程模式下。

4、垃圾回收:(mac)

在垃圾回收的环境下,是不需要自动释放池的。你可能写了一个 framework ,它被设计用来在垃圾回收环境和引用计数环境下都可工作。在这种情况下,你可以使用自动释放池去提示回收器回收可能是合适的。在垃圾回收环境中,如果必要会发送一个 drain 消息到池子中去触发垃圾回收机制;然而,release,是一个空操作。在引用计数的环境中,drain 和 release 的效果是一样的。通常,你应该使用 drain 而不是 release。

5、什么时候使用@autoreleasepool  核心思想

(1)写基于命令行的的程序时,就是没有UI框架,如 AppKit 等 Cocoa 框架时。

(2)当我们的应用有需要创建大量的临时变量的时候,可以是用 @autoreleasepool 来减少内存峰值。

(3)自动释放池可以延长对象的声明周期,如果一个事件周期很长,比如有一个很长的循环逻辑,那么一个临时变量可能很长时间都不会被释放,一直在内存中保留,那么内存的峰值就会一直增加,但是其实这个临时变量是我们不再需要的。这个时候就通过创建新的自动释放池来缩短临时变量的生命周期来降低内存的峰值。

例如:or (int i = 0; i < count; i++) {

@autoreleasepool {

6、release和drain的区别

(1)当我们向自动释放池 pool 发送 release 消息,将会向池中临时对象发送一条 release 消息,并且自身也会被销毁

(2)向它发送drain消息时,将会向池中临时对象发送一条release消息。  

官方解释:

release

(1)释放并且出栈接收者。(ARC)

drain

(1)在引用计数环境中,会释放并且出栈接受者。

(2)在垃圾回收环境中,会触发垃圾回收机制如果上次分配的内存集合大于当前的阈值。

7、autoreleasepool 底层实现

再看一下runtime 中 Autoreleasepool 的结构,通过阅读源码可以看出 Autoreleasepool 是一个由 AutoreleasepoolPage 双向链表的结构,其中 child 指向它的子 page,parent 指向它的父 page。

8、并且每个 AutoreleasepoolPage 对象的大小都是 4096 个字节。

9、AutoreleasepoolPage 通过压栈的方式来存储每个需要自动释放的对象。

10、一个线程的自动释放池是一个指针堆栈 这句话很关键

每一个指针或者指向被释放的对象,或者是自动释放池的 POOL_BOUNDARY,POOL_BOUNDARY 是自动释放池的边界。 一个池子的 token 是指向池子 POOL_BOUNDARY 的指针。当池子被出栈的时候,每一个高于标准的对象都会被释放掉。 堆栈被分成一个页面的双向链表。页面按照需要添加或者删除。 本地线程存放着指向当前页的指针,在这里存放着新创建的自动释放的对象。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值