概念:并发并行、同步异步(包括事件循环)、阻塞非阻塞、锁

一. 并发并行

并发:系统具有同一时间段内处理多个任务的能力。

并行:系统具有在同一时刻处理多个任务的能力。

二. 同步 异步 阻塞 非阻塞

1. 进程间的通信层面

进程间的通信时通过 send() 和 receive() 两种基本操作完成的。具体如何实现这两种基础操作,存在着不同的设计。

消息的传递有可能是阻塞的非阻塞的 -- 也被称为同步异步的

1. 阻塞式发送(blocking send). 发送方进程会被一直阻塞, 直到消息被接受方进程收到。

2. 非阻塞式发送(nonblocking send)。 发送方进程调用 send() 后, 立即就可以其他操作。

3. 阻塞式接收(blocking receive) 接收方调用 receive() 后一直阻塞, 直到消息到达可用。

4. 非阻塞式接受(nonblocking receive) 接收方调用 receive() 函数后, 要么得到一个有效的结果, 要么得到一个空值, 即不会被阻塞。

总结:也就是说, 从进程级通信的维度讨论时, 阻塞和同步(非阻塞和异步)就是一对同义词, 且需要针对发送方和接收方作区分对待。

2. 在 IO 系统调用层面( IO system call )层面

原文链接:https://blog.csdn.net/historyasamirror/article/details/5778378

作者的理论描述和比喻都生动形象。

这里为了方便自己快速回顾,在自认为理解作者原意的基础上,做了所谓的提炼,但是显然是有理解不完全或者错误的地方,希望以后自己看到或者过客看到能指正。

如原文作者所述,这里讨论的背景是Linux环境下的network IO。

总览

进程通信层面, 阻塞/非阻塞, 同步/异步基本是同义词, 但是需要注意区分讨论的对象是发送方还是接收方。

主要差别在真实IO层面。所谓真实,是因为我们常说的 阻塞/非阻塞/同步/异步IO,其实更具体应该分为两个阶段,如下:

当发起一个IO请求的时候,会经历两个阶段:

1. 等待数据准备

2. 数据准备好后将数据从kernel(内核)拷贝到进程中

所谓的真实的IO层面指的是数据从内核态拷贝到用户态。

阻塞和非阻塞

区别在于**准备数据阶段**(第一阶段)会不会将进程阻塞。

阻塞IO

发起请求后,在这两个阶段进程都会被阻塞

非阻塞IO

发起请求后,在准备数据阶段请求立即返回,因此进程不会被阻塞,但是进程会不断询问数据有没有准备好,准备好之后就发起数据接收的请求,开始第二阶段的数据拷贝,这个阶段中进程仍然会被阻塞

同步和异步

区别在于在进行**IO operation**(第二阶段)的时候会不会将进程阻塞(这里指的是真实的数据IO的时候,比如在将kernel中准备好的数据拷贝到进程中的时候,当在网络IO的时候应该也是类似的意思)。

同步IO:

在做IO operation(指的第二阶段)的时候会阻塞进程

异步IO:

在做IO operation(指的第二阶段)的时候不会阻塞进程,等到整个过程都完成了。

因此,单纯的阻塞IO和非阻塞IO都是同步IO,因为非阻塞IO在第二阶段的时候仍然是被阻塞的,只不过在第一阶段等待数据拷贝的时候是非阻塞的。

三. 协程

https://www.fanhaobai.com/2017/11/synchronised-asynchronized-coroutine.html

1. 对于操作系统来说只有进程和线程,协程的控制由应用程序显式调度,非抢占式的

2. 协程的执行最终靠的还是线程,应用程序来调度协程选择合适的线程来获取执行权

3. 切换非常快,成本低。一般占用栈大小远小于线程(协程 KB 级别,线程 MB 级别),所以可以开更多的协程

4. 协程比线程更轻量级

5. 协程可以理解为用户态的线程

事件循环 event loop

https://rgb-24bit.github.io/blog/2019/python-coroutine-event-loop.html

https://www.bilibili.com/video/av37759434/ [这个是视频,虽然是英文但是有图画还是挺好懂的]

https://zhuanlan.zhihu.com/p/33058983 [这个是一篇知乎上的文章,写得也更详细,也比较易懂]

涉及名称:

+ task queue:任务队列

+ stack:执行栈,负责执行代码,例如渲染,IO等。

+ event loop:事件循环(这其实是一个过程,这个过程被称为事件循环)

event loop 实际上是针对于异步而言的

简述:

当主线程遇到一个异步的事件(协称或者多线程之类的),会将这些任务挂起,并stack中继续执行其他的任务。等到异步事件返回结果后,该事件就会被放到task queue的队尾。当stack中的任务执行完了之后,主线程就把task queue的队首的事件拿出来,并把这个事件的回调放入stack中执行。这整个循环往复的过程就被称为事件循环。

代码举例:

```

console.log('1');

setTimeOut(function cb(){

  console.log("2");

  }, 0);

console.log('3')

打印结果:

1

3

2

```

在上面这个例子中,明明setTimeOut延时就0,应该打印结果是 1 2 3 才对吧。但是其实,setTimeOut算是个异步,所以虽然延时0,但是它的回调函数cb(也许不能这么叫,但是感觉起来对于像我这样的小白可能容易理解一点)依然被放到了task queue而不是直接进入到stack中,然后event loop等到stack中的所有任务都执行完了,再把task queue中的function cb拿到stack中,然后才执行这个函数。

在py中,我们通过selector注册了事件和回调,但是它并不会自动执行回调, 所以需要我们主动实现一个事件循环,当当前任务结束了,去主动调用它的回调。(至于里面具体的大概可以想象一下,可能是注册完了事件和回调之后,当当前的事件执行之后,回调会被放到task queue,但是并不会被放到stack中,因此这个回调并不会被执行,需要我们实现event loop来把这个回调函数从task queue放到stack中去)

四. GIL锁

http://cenalulu.github.io/python/gil-in-python/

并不是所有的解释器都有GIL锁,例如CPython中有,但是JPython总就没有

全局解释锁 加在解释器上的,为了在解释器层面保证线程安全,所以每个线程想要执行的时候必须获取GIL锁,但是这个锁只有一个,所以导致多线程其实每个cpu只有一个内核可以被使用,所以如果程序是cpu密集型的时候,反而可能不如串行来得效率高,因为多出了许许多多的context switch的时间,如果是io密集型的,那多线程还是很有用的。

线程互斥锁和GIL的区别

1. 线程互斥锁是Python代码层面的锁,解决Python程序中多线程共享资源的问题(线程数据共共享,当各个线程访问数据资源时会出现竞争状态,造成数据混乱);

2. GIL是Python解释层面的锁,解决解释器中多个线程的竞争资源问题(多个子线程在系统资源竞争是,都在等待对象某个部分资源解除占用状态,结果谁也不愿意先解锁,然后互相等着,程序无法执行下去)。

链接:https://juejin.im/post/5b977e5c5188255c996b6fad

解决GIL锁问题

由于python年岁已大,现在很多包都是基于GIL锁的,所以现在去掉可能不太容易。

但是我们可以对不同任务采用不同办法。如下:

1. cpu密集型:可以考虑换一个,别用py了。

2. IO密集型:多线程 或者 多进程(消耗大,通信不方便)+协程

你可能感兴趣的:(概念:并发并行、同步异步(包括事件循环)、阻塞非阻塞、锁)