0%

学习多线程开发前,我们需要知道很多的概念,这也是我曾经比较困惑的,至少别人问我的时候,我说不清楚,下面就针对这些概念总结下:

  • 进程

    操作系统早已经是多任务的了,早期她还有个名字叫多道程序;正是这个伟大的发明,我们才能享受一边听歌,一边写文档,一边聊天的乐趣;其实我们运行的程序都是来回切换的,只不过速度过快,我们无法感知而已,如何切换是由操作体统调度的,有相应的调度算法。我们只需要明白应用程序开启后,操作系统会为之开辟一个进程,她拥有地址空间,数据,以及各种资源等,当进程终止时,这些创建的资源被销毁,系统的资源被释放或者关闭。进程(程序)每隔一段时间就会暂停,保存下他的工作环境,然后开始运行另外一个进程,恢复他的现场,执行这个进程的任务,就这样循环的暂停,恢复,暂停,恢复…很多系统使用的是时间片轮转调度,也就是说每个进程(程序)运行的时间是平均分配的;

    作业调度算法补充:
    先进先出(FIFO),最短作业优先法(SJF),最高响应比优先法(HRN),定时轮转法,优先数法,事件驱动法,各种不同类型作业搭配调度算法等.

  • 线程

    我们已经知道了进程是被操作系统调度的单位,是一个应用的象征,而线程时操作系统能够调度的最小单位,他被包含在进程之中,是进程的实际运作单位;一个进程中可以并发多个线程,每条线程并行执行不同的任务,Unix System中称为轻量级进程;也就是说线程是属于进程的,同一个进程中的线程共享进程中的全部系统资源,如虚拟地址空间,IO,信号等;但同一和进程中的多个线程有各自的调用栈,自己的寄存器环境等;

    多线程:程序启动后,系统为程序创建了一个进程,每一个进程都会有一个主执行线程,且被默认创建,为了充分利用CPU,我们也会创建线程,做一些想做的事情,这样就会出现一个进程包含多个线程的情况;

在多核或多CPU,或支持Hyper-threading的CPU上使用多线程程序设计的好处是显而易见,即提高了程序的执行吞吐率。在单CPU单核的计算机上,使用多线程技术,也可以把进程中负责IO处理、人机交互而常备阻塞的部分与密集计算的部分分开来执行,编写专门的workhorse线程执行密集计算,从而提高了程序的执行效率。

  • 并发

    对于单核CPU来说的,操作系统会按照一定的调度算法切换线程,让各个线程都能执行的方式叫做并发。描述的是任务之间的关系。

  • 并行

    对于多核的CPU来说,可以同时让两个以上的线程同时运行,这种执行方式叫做并行。

  • 同步、异步

    他们是描述任务何时返回(完成)和派发任务所在线程之间的关系问题的;
    比如在主线程使用xx()函数派发了一个新任务,如果只有当xx()函数执行完才能返回到主线程,那么这个函数xx就是同步的,特点是阻塞线程;
    如果,在主线程派发了之后,立马返回了,无需等待任务的完成,那么这个函数就是异步的;

  • 临界区

    多线程环境下,可能会有资源被多个线程访问的情况,这样一来二去,这个资源就变得不可信了,变了质了,不再是准确的了;这当然也不是我们想要的;后续博客会解决这个不可信问题,也就是多线程同步问题!(ps :这里说的同步跟上面提到的同步,异步不是一个概念哦!)

参考的文档:

NSTimer 是 iOS 很常用的一个类,可以很方便的做延迟任务,也可以做周期性的轮询。不过我在写轮播图的时候却发现 Timer造成了内存泄漏!

背景

我在 ViewController 里使用了 Timer,结果发现 pop 掉该控制器后,他的 dealloc 却没有调用!这是个内存泄露的问题,比较严重,要尽快解决!

分析原因

  1. 在使用 Timer 的时候,我们需要给他指定一个 Target,而 Timer 的内部会持有该 Target,原因可想而知,当 Timer 到时间之后就要触发目标方法,如果不持有 Target 的话,就可能会导致 Target 已经释放,无法达到调用目标方法的效果;在没有ARC的年代,如果用assign势必会造成野指针崩溃的,因此要持有 Target;

  2. 要知道 Timer 的运行依赖于 Runloop 的驱动,即 fire 之前需要加入到 Runloop 中,但这样一来 Timer 就被 Runloop 持有了,除非调用 invalidate,才会从 Runloop 中移除;

  3. 实际使用的时候,Target 就是我的 ViewController,我是在 ViewController 的 dealloc 里将 Timer invalidate 掉的;

如上所述,Target 会被 Timer 持有,Timer 会被 Runloop 持有,结果就导致了 ViewController 一直被持有着,所以在 ViewController 的 dealloc 里 invalidate 掉 Timer 是没作用的,压根不会走这个逻辑,所以当导航控制器 pop 之后,页面消失了,但是内存却没释放,这就导致了内存泄漏!

解决问题

如果能在 pop ViewController 之前将 Timer invalidate 掉,就可以解除 Timer 对 ViewController 的持有,让 ViewController 正常释放;

简单的做法是在控制器的 viewwilldisappear 中 invalidate,不过一般情况下就要对应的在 viewwillappear 中重新创建 Timer,这么做一般情况都是可以的,但如果 Target 不是 ViewController 就需要多写点代码了;更糟糕的是,我遇到过调用了 viewwillappear 却没调用 viewwilldisappear 就要dealloc 的情况,这时情况就很糟糕了,看来需要一个优雅的解决办法才是完全之策。

要解决这个问题,就要从打破持有上解决,Runloop 持有 Timer 估计是没办法做文章了,那能不能想办法把 Target 转移下呢? 是的,可以将自身作为 Target,然后通过 block 回调出去?外部调用使用 weak-storng dance ,不让 block
持有 target;似乎可行哦,立马行动:

1
2
3
4
+ (NSTimer *)scheduledWithTimeInterval:(NSTimeInterval)ti repeats:(BOOL)yesOrNo block:(void (^)())block
{
return [NSTimer scheduledTimerWithTimeInterval:ti target:self selector:@selector(ql_blockInvoke:) userInfo:[block copy] repeats:yesOrNo];
}

把传入的block当作userinfo穿过去,userinfo是个id类型的,故而可以传递一个block过去,block是在栈上分配的,所以必须copy到堆上,这样日后回调的时候才能找到它;

1
2
3
4
5
6
7
+ (void)ql_blockInvoke:(NSTimer *)sender
{
void (^block)() = sender.userInfo;
if (block) {
block();
}
}

通过类别,我们给timer加上了这些方法;

使用

1
2
3
4
5
__weak __typeof(self)weakSelf = self;
NSTimer *timer = [NSTimer scheduledWithTimeInterval:animationDuration repeats:YES block:^{
__strong __typeof(weakSelf)strongSelf = weakSelf;
[strongSelf animationTimerDidFired];
}];

使用scheduledTimerWithTimeInterval方法创建的timer会自动加入到当前runloop中,并在interval之后fire;如果不想立马开始可以这样做:

1
[timer setFireDate:[NSDate distantFuture]];

记得在target的dealloc里把这个timer invalidate即可;

增加了几个方便的方法

1
2
3
- (void)pauseTimer;
- (void)resumeTimer;
- (void)resumeTimerAfterTimeInterval:(NSTimeInterval)interval;

出于对前端的兴趣和 markdown 的热衷,于是今天摸索着搭建了一个使用 markdown 写文章,经过 jekyll 解析生成静态站点,发布到 github 的博客,折腾过程中还学习了些前端的知识!

在此之前曾在 新浪博客CSDN 上写过几篇文章,可是未能坚持下去。

写博客的过程也是自我认识和能力提升的一个过程,因为要把一个不熟悉的东西说清楚真的很困难,有困难才会有进步,不是嘛?

版本记录

  • 17 年 12 月换成 hexo,主题则采用了 hexo-theme-yaris.
  • 17 年 7 月自己编写 jekyll 主题 Yaris.
  • 16 年 12 月开始使用独立域名 debuly.cn.
  • 15 年 7 月使用 octopress 搭建了我的博客,从此之后再无安宁之日,没事了就来折腾我的博客.