深刻理解并发/并行,阻塞/非阻塞,同步/异步

1. 阻塞,非阻塞

首先,阻塞这个词来自操做系统的线程/进程的状态模型中,以下图: 输入图片说明算法

一个线程/进程经历的5个状态,建立,就绪,运行,阻塞,终止。各个状态的转换条件如上图,其中有个阻塞状态,就是说当线程中调用某个函数,须要IO请求,或者暂时得不到竞争资源的,操做系统会把该线程阻塞起来,避免浪费CPU资源,等到获得了资源,再变成就绪状态,等待CPU调度运行。并发

阻塞调用是指调用结果返回以前,调用者会进入阻塞状态等待。只有在获得结果以后才会返回。异步

非阻塞调用是指在不能马上获得结果以前,该函数不会阻塞当前线程,而会马上返回。socket

阻塞调用:好比 socket 的 recv(),调用这个函数的线程若是没有数据返回,它会一直阻塞着,也就是 recv() 后面的代码都不会执行了,程序就停在 recv() 这里等待,因此通常把 recv() 放在单独的线程里调用。函数

非阻塞调用:好比非阻塞socket 的 send(),调用这个函数,它只是把待发送的数据复制到TCP输出缓冲区中,就马上返回了,线程并不会阻塞,数据有没有发出去 send() 是不知道的,不会等待它发出去才返回的。oop

拓展:死锁

若是线程始终阻塞着,永远得不到资源,因而就发生了死锁。操作系统

好比A线程要X,Y资源才能继续运行,B线程也要X,Y资源才能运行,但X,Y同时只能给一个线程用(即互斥条件)用的时候其余线程又不能抢夺。线程

A 有 X,等待 Y。 B 有 Y,等待 X。code

因而A,B发生了循环等待,形成死锁。给用户的感受就是程序卡着不动了。排序

在写代码的时候要特别注意共享资源的使用,用信号量控制好,避免形成死锁。死锁的解除有个著名的银行家算法

阻塞和挂起:阻塞是被动的,好比抢不到资源。挂起是主动的,线程本身调用 suspend() 把本身退出运行态了,某些时候调用 resume() 又恢复运行。

线程执行完就会被销毁,若是不想线程被频繁的建立,销毁,怎么办?能够给线程里面写个死循环,或者让线程有任务的时候执行,没任务的时候挂起,就像iOS中的 runloop 机制同样。线程就不会随便的终止了。

2. 同步,异步

同步:在发出一个同步调用时,在没有获得结果以前,该调用就不返回。

异步:在发出一个异步调用后,调用者不会马上获得结果,该调用就返回了。

同步例子

int n = func();
next();
// func() 的结果没有返回,next() 就不会执行,直到 func() 运行完。

异步例子

func(callback);
next();
...

void callback(int n)     // func 结果回调
{
  int k = n;
}
// func() 执行后,还没得出结果就当即返回,而后执行 next() 了
// 等到结果出来,func() 回调 callback() 通知调用者结果。

同步的定义看起来跟阻塞很像,可是同步跟阻塞是两个概念,同步调用的时候,线程不必定阻塞,调用虽然没返回,但它仍是在运行状态中的,CPU极可能还在执行这段代码,而阻塞的话,它就确定不在CPU中跑这个代码了。这就是同步和阻塞的区别。同步是确定能够在,阻塞是确定不在。

异步和非阻塞的定义比较像,二者的区别是异步是说调用的时候结果不会立刻返回,线程可能被阻塞起来,也可能不阻塞,二者不要紧。非阻塞是说调用的时候,线程确定不会进入阻塞状态。

同步阻塞调用:得不到结果不返回,线程进入阻塞态等待。

同步非阻塞调用:得不到结果不返回,线程不阻塞一直在CPU运行。

异步阻塞调用:去到别的线程,让别的线程阻塞起来等待结果,本身不阻塞。

异步非阻塞调用:去到别的线程,别的线程一直在运行,直到得出结果。

3. 并发,并行

并发是指一个时间段内,有几个程序都在同一个CPU上运行,但任意一个时刻点上只有一个程序在处理机上运行。

并行是指一个时间段内,有几个程序都在几个CPU上运行,任意一个时刻点上,有多个程序在同时运行,而且多道程序之间互不干扰。 二者区别以下图

输入图片说明

输入图片说明 并行是多个程序在多个CPU上同时运行,任意一个时刻能够有不少个程序同时运行,互不干扰。

并发是多个程序在一个CPU上运行,CPU在多个程序之间快速切换,微观上不是同时运行,任意一个时刻只有一个程序在运行,但宏观上看起来就像多个程序同时运行同样,由于CPU切换速度很是快,时间片是64ms(每64ms切换一次,不一样的操做系统有不一样的时间),人类的反应速度是100ms,你还没反应过来,CPU已经切换了好几个程序了。

举个例子吧,并行就是,多我的,有人在扫地,有人在作饭,有人在洗衣服,扫地,作饭,洗衣服都是同时进行的。 并发就是,有一我的,这我的一下子扫地,一下子作饭,一下子洗衣服,他在这3件事中来回作,同一时刻只作一件事,不是同时作的,但最后3件事均可以作完。

时间片大小的选取 时间片取的小,假设是20ms,切换耗时假设是 10ms。 那么用户感受不到多个程序之间会卡,响应很快,由于切换太快了,可是CPU的利用率就低了,20 / (20 + 10) = 66% 只有这么多,33%都浪费了。

时间片取的大,假设是200ms,切换耗时是 10ms 那么用户会以为程序卡,响应慢,由于要200ms后才轮到个人程序运行,可是CPU利用率就高了,200 / (200 + 10) = 95% 有这么多被利用的。

因此时间片取太大或者过小都很差,通常在 10 - 100 ms 之间。

CPU调度策略

在并发运行中,CPU须要在多个程序之间来回切换,那么如何切换就有一些策略

3.1 先来先服务 - 时间片轮转调度

这个很简单,就是谁先来,就给谁分配时间片运行,缺点是有些紧急的任务要好久才能获得运行。

3.2 优先级调度

每一个线程有一个优先级,CPU每次去拿优先级高的运行,优先级低的等等,为了不等过久,每等必定时间,就给线程提升一个优先级

3.3 最短做业优先

把线程任务量排序,每次拿处理时间短的线程运行,就像我去银行办业务同样,个人事情很快就处理完了,因此让我插队先办完,后面时间长的人先等等,时间长的人就很可贵到响应了。

3.4 最高响应比优先

用线程的等待时间除以服务时间,获得响应比,响应比小的优先运行。这样不会形成某些任务一直得不到响应。

3.5 多级反馈队列调度

有多个优先级不一样的队列,每一个队列里面有多个等待线程。 CPU每次从优先级高的遍历到低的,取队首的线程运行,运行完了放回队尾,优先级越高,时间片越短,即响应越快,时间片就不是固定的了。 队列内部仍是用先来先服务的策略。

相关文章
相关标签/搜索