3.5.1同期および非同期I / O

イベント駆動型と非同期IO

私たちは、サーバー処理モデルプログラムを記述する場合、通常、次のモデルです。

(1)各要求を処理するための新しいプロセスを作成する要求を受信します。

(2)各要求を処理するために新しいスレッドを作成するための要求を受け取ります。

(3)I / Oモードを非ブロックすることにより、ハンドルのリクエストにメインプロセスを取得するイベントのリストに、各リクエストの受信、

いくつかの方法で上に、それぞれがその利点を持っています、

(1)法、オーバーヘッドが比較的大きいために、新しいプロセスを作成し、それがサーバーのパフォーマンスを達成するのは比較的低いが、比較的単純である原因になります。

関与スレッド同期(2)の方法は、このようなデッドロックのような顔の問題が可能性があります。

(3)方法、前の二つのより複雑なアプリケーション・コード、ロジックを書きます。

すべての要因を考慮すると、一般的に最初の(3)の方法はほとんどのWebサーバが使用方法であることに合意

イベント駆動型モデル

UIプログラミングでは、多くの場合、それを得るためにマウスクリック、マウスクリックまずどのように対応するの?
方法1:マウスクリックが、その後、このアプローチは、次のような欠点があるかどうかを検出循環されたスレッド作成し
、無駄1. CPUリソースを、マウスクリックの頻度が非常に小さいかもしれませんが、まだ糸ループ検出をスキャンされていたであろうがCPUは、リソースの無駄の多くの原因となります。スキャン・クリック・インタフェースはそれをブロックしている場合はどうなりますか?
それがブロックされている場合2.は、次のように我々は唯一のマウスクリックをスキャンする場合は、そのような問題ではなく、キーボードが原因マウスをスキャンするとき、あなたはキーボードのスキャン行かないかもしれませんブロックされてに押されているスキャンします;
3。あなたは、機器のサイクルをスキャンする必要がある場合は順番に、応答時間の問題につながるれ、非常に大きい。
そのため、この方法は非常に悪いです。

第二の方法:イベント駆動型モデルは
UIプラットフォームの多くは、イベントダウンマウスを表してのonClick()イベントを提供しますようUIプログラムのほとんどは、イベント駆動型モデルである本。次のように一般的な考えのイベント駆動型モデルがある:
1.キューイベント(メッセージ)がある。
2.マウスは、このキューは、クリックイベント(メッセージ)を増加させるため;
3.そこサイクルが連続し、イベントキューから削除さに応じて、イベント、異なるそのようなのonClick()にonKeyDown(として機能)等を呼び出す;
4.イベント(メッセージ)は、それぞれが独自のメッセージハンドラを有するように、一般に、各記憶各処理関数ポインタです。

イベント駆動型モデル

イベント駆動型プログラミングは、プログラム実行フローは、外部イベントによって決定されるプログラミングパラダイムです。外部イベントは、適切な処置をトリガするために、コールバック機構を使用して発生したときに、イベントループを含んでいます。二つの他の一般的なプログラミングパラダイム(シングルスレッド)同期およびマルチスレッドプログラミング。

のは、比較してみましょうと、シングルスレッド、マルチスレッドとイベント駆動型のプログラミングモデルの一例とそれを対比。次の図に示す時間これらの三つのモードプログラム上で行われる作業。このプログラムは、I / Oの彼らの操作を阻止するために、各タスクが待っている完了するために3つのタスクを持っています I / O操作の時間ブロックは、グレーのボックスに費やさマークされています。

同期非同期モデル

ねじ切り同期モデル、タスクの実行順序にシングルスレッド。I / Oため、タスクは、他のすべてのタスクは、それは彼らが実行できるようにするために、完了した後まで待たなければなりませんブロックされた場合。この明確な実行順序と行動の一連の処理は、推論を描画するのは非常に簡単です。そこタスクと相互依存の間には関係がありませんが、まだお互いを待つ必要がある場合、これは、不必要な手順は動作速度を減少させます。

在Multi-threaded多线程版本中,这3个任务分别在独立的线程中执行。这些线程由操作系统来管理,在多处理器系统上可以并行处理,或者在单处理器系统上交错执行。这使得当某个线程阻塞在某个资源的同时其他线程得以继续执行。与完成类似功能的同步程序相比,这种方式更有效率,但程序员必须写代码来保护共享资源,防止其被多个线程同时访问。多线程程序更加难以推断,因为这类程序不得不通过线程同步机制如锁、可重入函数、线程局部存储或者其他机制来处理线程安全问题,如果实现不当就会导致出现微妙且令人痛不欲生的bug。

在事件驱动版本的程序中,3个任务交错执行,但仍然在一个单独的线程控制中。当处理I/O或者其他昂贵的操作时,注册一个回调到事件循环中,然后当I/O操作完成时继续执行。回调描述了该如何处理某个事件。事件循环轮询所有的事件,当事件到来时将它们分配给等待处理事件的回调函数。这种方式让程序尽可能的得以执行而不需要用到额外的线程。事件驱动型程序比多线程程序更容易推断出行为,因为程序员不需要关心线程安全问题。

当我们面对如下的环境时,事件驱动模型通常是一个好的选择:

  1. 程序中有许多任务,而且…
  2. 任务之间高度独立(因此它们不需要互相通信,或者等待彼此)而且…
  3. 在等待事件到来时,某些任务会阻塞。

当应用程序需要在任务间共享可变的数据时,这也是一个不错的选择,因为这里不需要采用同步处理。

网络应用程序通常都有上述这些特点,这使得它们能够很好的契合事件驱动编程模型。

同步与异步I/O的底层机制(让你月薪直逼35K)

同步IO和异步IO,阻塞IO和非阻塞IO分别是什么,到底有什么区别?不同的人在不同的上下文下给出的答案是不同的。所以先限定一下本文的上下文。

本文讨论的背景是Linux环境下的network IO。

概念说明

要了解同步与异步I/O的底层机制,需要先知道以下几个概念:

  • 用户空间和内核空间
  • 进程切换
  • 进程的阻塞
  • 文件描述符
  • 缓存I/O

用户空间与内核空间

现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方)。操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操心系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。针对linux操作系统而言,将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为内核空间,而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为用户空间。

进程切换

为了控制进程的执行,内核必须有能力挂起正在CPU上运行的进程,并恢复以前挂起的某个进程的执行。这种行为被称为进程切换。

因此可以说,任何进程都是在操作系统内核的支持下运行的,是与内核紧密相关的。

从一个进程的运行转到另一个进程上运行,这个过程中经过下面这些变化:

  1. 保存 处理机上下文,包括程序计数器和其他寄存器。
  2. 更新PCB信息。
  3. 把进程的PCB移入相应的队列,如就绪、在某事件阻塞等队列。
  4. 选择另一个进程执行,并更新其PCB。
  5. 更新内存管理的数据结构。
  6. 恢复处理机上下文。

注:进程控制块(Processing Control Block),是操作系统核心中一种数据结构,主要表示进程状态。其作用是使一个在多道程序环境下不能独立运行的程序(含数据),成为一个能独立运行的基本单位或与其它进程并发执行的进程。或者说,OS是根据PCB来对并发执行的进程进行控制和管理的。 PCB通常是系统内存占用区中的一个连续存区,它存放着操作系统用于描述进程情况及控制进程运行所需的全部信息

进程的阻塞

正在执行的进程,由于期待的某些事件未发生,如请求系统资源失败、等待某种操作的完成、新数据尚未到达或无新工作做等,则由系统自动执行阻塞原语(Block),使自己由运行状态变为阻塞状态。可见,进程的阻塞是进程自身的一种主动行为,也因此只有处于运行态的进程(获得CPU),才可能将其转为阻塞状态。当进程进入阻塞状态,是不占用CPU资源的

文件描述符fd

文件描述符(File descriptor)是计算机科学中的一个术语,是一个用于表述指向文件的引用的抽象化概念。

文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。

缓存 I/O

缓存 I/O 又被称作标准 I/O,大多数文件系统的默认 I/O 操作都是缓存 I/O。在 Linux 的缓存 I/O 机制中,操作系统会将 I/O 的数据缓存在文件系统的页缓存( page cache )中,也就是说,数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。

缓存 I/O 的缺点:
数据在传输过程中需要在应用程序地址空间和内核进行多次数据拷贝操作,这些数据拷贝操作所带来的 CPU 以及内存开销是非常大的。

I/O模式

刚才说了,对于一次IO访问(以read举例),数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。所以说,当一个read操作发生时,它会经历两个阶段:
1. 等待数据准备 (Waiting for the data to be ready)
2. 将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)

正式因为这两个阶段,linux系统产生了下面五种网络模式的方案。

  • 阻塞 I/O(blocking IO)
  • 非阻塞 I/O(nonblocking IO)
  • I/O 多路复用( IO multiplexing)
  • 信号驱动 I/O( signal driven IO)
  • 异步 I/O(asynchronous IO)

注:由于signal driven IO在实际中并不常用,所以我这只提及剩下的四种IO Model。

阻塞 I/O(blocking IO)

在linux中,默认情况下所有的socket都是blocking,一个典型的读操作流程大概是这样:

IOをブロック

当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据(对于网络IO来说,很多时候数据在一开始还没有到达。比如,还没有收到一个完整的UDP包。这个时候kernel就要等待足够的数据到来)。这个过程需要等待,也就是说数据被拷贝到操作系统内核的缓冲区中是需要一个过程的。而在用户进程这边,整个进程会被阻塞(当然,是进程自己选择的阻塞)。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来。

所以,blocking IO的特点就是在IO执行的两个阶段都被block了。

非阻塞 I/O(nonblocking IO)

linux下,可以通过设置socket使其变为non-blocking。当对一个non-blocking socket执行读操作时,流程是这个样子:

IOをノンブロッキング

当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。

所以,nonblocking IO的特点是用户进程需要不断的主动询问kernel数据好了没有。

I/O 多路复用( IO multiplexing)

IO multiplexing就是我们说的select,poll,epoll,有些地方也称这种IO方式为event driven IO(事件驱动I/O)。select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。它的基本原理就是select,poll,epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。

IO多重化

当用户进程调用了select,那么整个进程会被block而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。

所以,I/O 多路复用的特点是通过一种机制一个进程能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,select()函数就可以返回。

这个图和blocking IO的图其实并没有太大的不同,事实上,还更差一些。因为这里需要使用两个system call (select 和 recvfrom),而blocking IO只调用了一个system call (recvfrom)。但是,用select的优势在于它可以同时处理多个connection。

所以,如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。)

在IO multiplexing Model中,实际中,对于每一个socket,一般都设置成为non-blocking,但是,如上图所示,整个用户的process其实是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。

异步 I/O(asynchronous IO)

Linux下的asynchronous IO其实用得很少。先看一下它的流程:

非同期IO

用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。

总结

blocking和non-blocking的区别

调用blocking IO会一直block住对应的进程直到操作完成,而non-blocking IO在kernel还未准备好数据的情况下会立刻返回。

synchronous IO和asynchronous IO的区别

在说明synchronous IO和asynchronous IO的区别之前,需要先给出两者的定义。POSIX的定义是这样子的:

  • A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;
  • An asynchronous I/O operation does not cause the requesting process to be blocked;

浅译

  • 一个同步I/O操作导致正在发起请求的进程阻塞直到I/O操作完成;
  • 一个异步I/O操纵不会导致正在发起请求的进程被阻塞;

两者的区别就在于synchronous IO做”IO operation”的时候会将process阻塞。按照这个定义,之前所述的blocking IO,non-blocking IO,IO multiplexing都属于synchronous IO。同步I/O

注:non-blocking IO在执行recvfrom这个system call的时候,如果kernel的数据没有准备好,这时候不会block进程。但是,当kernel中数据准备好的时候,recvfrom会将数据从kernel拷贝到用户内存中,这个时候进程是被block了,在这段时间内,进程是被block的。

而asynchronous IO则不一样,当进程发起IO 操作之后,就直接返回再也不理睬了,直到kernel发送一个信号,告诉进程说IO完成。在这整个过程中,进程完全没有被block。

各个IO Model的比较如图所示:

5つのモデルの比較

上の写真を通して、あなたは、非ブロッキングIOと非同期IOの違いを見つけることができますまだ非常に明白です。非ブロックIOには、ほとんどの時間は、プロセスはブロックされませんが、それはまだチェックして主導権を取るためのプロセスが必要であり、データは、プロセスの完了後に準備ができたときにも、ユーザメモリにデータをコピーするため再びコールのrecvfromへのイニシアチブを取る必要があるが、 。非同期IOは完全に異なっています。それは他の人(カーネル)に引き渡さ全体のユーザー・プロセスIO操作が完了するように、そして最後まで他人を合図です。一方、プロセスはIO操作の状態を確認するために、ユーザを必要としない、あなたは、データをコピーするためのイニシアチブを取る必要はありません。

おすすめ

転載: www.cnblogs.com/infinitecodes/p/12145412.html