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

深入理解并发/并行,阻塞/非阻塞,同步/异步
【并发编程】深入理解——阻塞/非阻塞、同步/异步、并发/并行的概念
[并发概念] 同步与异步、阻塞与非阻塞

1. 阻塞,非阻塞

阻塞是关于线程/进程的.

  • 阻塞调用是指调用结果返回之前,调用者会进入阻塞状态等待。只有在得到结果之后才会返回。
  • 非阻塞调用是指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。

阻塞这个词来自操作系统的线程/进程的状态模型中,如下图:
深入理解并发/并行,阻塞/非阻塞,同步/异步_第1张图片
一个线程/进程经历的5个状态,创建,就绪,运行,阻塞,终止。各个状态的转换条件如上图,其中有个阻塞状态,就是说当线程中调用某个函数,需要IO请求,或者暂时得不到竞争资源的,操作系统会把该线程阻塞起来,避免浪费CPU资源,等到得到了资源,再变成就绪状态,等待CPU调度运行。

阻塞调用:比如 socket 的 recv(),调用这个函数的线程如果没有数据返回,它会一直阻塞着,也就是 recv() 后面的代码都不会执行了,程序就停在 recv() 这里等待,所以一般把 recv() 放在单独的线程里调用。
非阻塞调用:比如非阻塞socket 的 send(),调用这个函数,它只是把待发送的数据复制到TCP输出缓冲区中,就立刻返回了,线程并不会阻塞,数据有没有发出去 send() 是不知道的,不会等待它发出去才返回的。

2. 同步, 异步

同步与异步通常用来形容一次调用,关注的是消息通信机制(被调用者是否主动告诉调用者结果)。

同步:同步调用一旦开始,调用者必须等到调用返回后,才能进行后续的行为。(也就是说,被调用者不会主动告诉结果,而是调用者主动等待调用的结果。)
异步:异步调用发出后,调用就会立即返回,告诉调用者我方收到请求已经去处理,调用者可以继续后续的操作。(异步调用的操作通常在另外一个线程内真实的执行,完成后主动通知调用者。)
深入理解并发/并行,阻塞/非阻塞,同步/异步_第2张图片

3. 同步/异步,阻塞/非阻塞的四种组合

同步与阻塞、异步与非阻塞是比较容易混淆的概念。同步等价于阻塞,异步等价于非阻塞是理解上的误区,是不正确的。

同步阻塞调用:得不到结果不返回,线程进入阻塞态等待。
同步非阻塞调用:得不到结果不返回,线程不阻塞一直在CPU运行。
异步阻塞调用:去到别的线程,让别的线程阻塞起来等待结果,自己不阻塞。
异步非阻塞调用:去到别的线程,别的线程一直在运行,直到得出结果。

例子:

同步阻塞:以烧水壶为例,按下烧水开关之后,我们什么也不做,就一直在那等着,这就叫阻塞;而同步是用来形容水壶的,它不会主动告诉我们结果,需要我们一直盯着水壶是否烧开。
同步非阻塞:非阻塞是指按下烧水开关之后,继续干其他事情。由于是同步的,水壶不会主动告诉我们水是否烧开。因此我们可以每隔几分钟去看下水壶情况。
异步阻塞:异步表示水壶具有提醒功能,发出声音。我们按下烧水开关之后,仍然坐等着,直到水壶发出提醒水烧开,我们将水壶取下来。
异步非阻塞:按下烧水开关后,我们就去干其他事情,听到水壶烧开声音后,取下水壶。

3. 并发,并行

并发与并行是两个相关(related)但不同(distinct)的概念,都是“同时做多件事”

  • 并发关注的点是多件事被同时(一段时期)做,但只有一件事情正在执行(单核)!
  • 并行关注的点是多个“人”在同时做事情。只有多核系统才能实现并行处理。

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

深入理解并发/并行,阻塞/非阻塞,同步/异步_第3张图片
深入理解并发/并行,阻塞/非阻塞,同步/异步_第4张图片

5、并发编程的实现方式(多线程/异步编程)

并发,就是同时做多件事情,所以多线程可以实现并发,单线程同样可以实现并发。

好的程序应该能充分利用有限的 CPU 资源,努力提高资源利用率,该需求可以有两种实现途径:

  • 通过多线程/多进程来实现。
  • 异步编程:
    线程/进程是稀缺资源,数量有限,且线程/进程上下文切换成本高。程序中的线程越多,上下文切换的成本越高,CPU 有效利用率越低!另外,在多线程/多进程模型中,需要考虑并发控制的问题,增加了编程的复杂度。
    那么如何实现不依赖多线程/多进程的并发编程呢?此时,异步编程就应运而生了!
    一方面,异步/非租塞通过减少对线程/进程的依赖,提高了 CPU 资源的有效利用率,同时避免了多线程/多进程中并发控制的问题;另一方面,异步编程可以降低编程的复杂度。

系统层面: 进程(Process),线程(Thread)

现代操作系统 Unix/Linux/Windows 都是支持“多任务”的操作系统,为并发编程提供了不同的任务模型——进程,线程,程序员可以借助多线程+多进程来实现并发编程。

线程是最小的执行单元,而进程由至少一个线程组成,线程/进程调度,如何时执行,执行多久,由操作系统来决定。数据共享和并发控制是多线程/多进程编程时面临的重要挑战,增加了编程的复杂度;另外,频繁的上下文切换,也会影响 CPU 的使用效率。

为了减少线程切换,会使用线程池来管理线程。

语言层面(异步编程): Channel,Coroutine(协程),Futures and Promises

并发编程的灵活度会极大的影响一门编程语言的表现力,比如:

  • Python 中的协程、async/wait,
  • C# 中的 Task,async/wait,
  • Go 中的 Channel。
  • 另外 future, promise, delay, deferred 也是编程语言中并发编程模型相关的术语。
Python 协程(coroutine)

协程能向多线程一样并发执行,但是避免了线程切换,所以执行效率很高。另外,因为协程之间是同步执行了,不需要像多线程编程那样进行并发控制,不需要加锁,所以比多线程编程简单。

  • 函数调用是通过调用栈来实现的,有一个入口,有一个返回值,调用顺序是明确的。
  • 但是协程不同,协程允许先中断、并跳转到其它地方执行,并在适当的时候返回来继续执行。
    深入理解并发/并行,阻塞/非阻塞,同步/异步_第5张图片
Python async/await

从 Python 3.5 开始引入了新的语法 async 和 await,可以让 coroutine(协程) 的代码更简洁易读。

import asyncio as aio

async def hello():
	print("Hello World")
	r = await asyncio.sleep(100) # 耗时 io
	print("Hello again!")

loop = aio.get_event_loop()
tasks = [hello(), hello()] # 初始化任务(其实就是创建 coroutine 对应的 generator)
loop.run_until_complete(aio.wait(tasks))
loop.close()

你可能感兴趣的:(网站开发,网络,并发,同步,异步,非阻塞)