BIO IO 모델, NIO 및 AIO

IO 무엇입니까

우리는 내부의 유닉스 시스템에서 모든 파일 인 것을 알고있다. 파일은 바이너리 스트림 만의 문자열입니다. 소켓 여부, 파이프 라인, 터미널, 우리를 위해, 모든 파일 스트림입니다. 정보 교환의 과정에서, 우리는 이러한 스트림의 동작에 대한 데이터를 전송 및 수신, I / O 동작 (입력 및 출력)라고 함 .

계산기는 어떻게되는 스트림을 작동 알고, 많은 스트림이있다? 관통는 파일 기술자 (FD)라고도하는이 동작은 파일 (스트림)의 정수 연산이다 정도, 정수가 fd.

시스템 호출은 다음 소켓의 작업의 나머지는이 기술자의 작동으로 전환 될 파일 기술자를 반환 통해 우리는, 소켓을 작성합니다.

사용자 공간과 커널 공간

현재 운영 시스템은 가상 메모리이고, 32 비트 시스템은 주소 공간 (가상 스토리지) 4G (32의 2 승)를 비트. 운영 체제 커널의 핵심은, 일반 응용 프로그램의 독립, 당신은 보호 된 메모리 공간에 액세스뿐만 아니라, 기본 하드웨어에 대한 모든 액세스 할 수 있습니다.

커널의 안전을 보장하기 위해 사용자가 직접 처리 커널 (커널)를 조작 할 수 있도록하기 위해, 가상 공간의 오퍼레이팅 시스템은 사용자 공간의 일부로서, 두 부분, 커널 공간 하나씩으로 분할된다.

리눅스 시스템, 커널 공간라는 커널 고 1G 바이트의 경우, 낮은 3G는 사용자 공간이라고 사용하는 각 프로세스에 대해 바이트있다.

동기 및 비동기

그것은 커널 조건 동기 및 비동기 응용 프로그램의 상호 작용을위한 것입니다.

동기화 프로세스는 IO 작업이 (비 차단)를 참조 완료하는 IO 운영과 (차단) 대기 또는 설문 조사를 트리거됩니다.

비동기 프로세스는 IO 작업을 트리거 한 후, 알림 프로세스 커널 IO 완료의 완료 후, 처리하는 커널에 자신의 일, IO을 직접 돌아갑니다.

동기 차단 및 비 차단 점, 그것은 여야가 그래서 비동기 비는 차단.

차단, 비 - 차단이 멀티 채널 다중화 IO, IO 동기이다. 전체의 CPU에 모두의 구현에 어떤 생각으로 IO IO를 운영하지만, 그들은 단지 완료 통지 신호를 기다리 만 사용자 스레드, 이것은 실제 비동기 IO이다. 폴링 스레드는 epoll 비동기 아니라, 폴링 아이 당겨 또는 환상 다이를 선택 사용한다.

비 차단 차단

차단 :는 CPU 처리를 위해 대기 한 후 CPU가 작업을 처리하는 것은 뒤에 작업을 수행하기 전에 완료됩니다.

비는 차단 : CPU가 후속 작업을 처리하기 위해 계속 위임을 처리하기 위해 후 작동 여부를 다시 수시로 조회가 완료되기 전에, 프로세스는 사실도 폴링이라고합니다.

스레드가 소켓 I / O 이벤트를 처리 할 수 ​​있기 때문에이 프로세스 여러하려는 경우, 비 블로킹 바쁜 폴링 방식으로, 다음의 의사 코드를 사용할 수있다 :

while true  
{  
    for i in stream[]  
    {  
        if i has data  
        read until unavailable  
    }  
}

위의 코드를 통해, 우리가 모든 스트림을 읽을 필요, 그것은 여러 흐름을 처리 할 수 ​​있습니다. 그러나 일을 그렇게 문제는 CPU의 타임 슬라이스 모든 스트림이 더 I / O 이벤트없는 경우 때문에, 안녕 낭비가있다.

여기에 유휴 CPU를 방지하기 위해, 우리는, 나는 많은 동시 스트림을 관찰 할 수있다,이 기관은 매우 강력 스레드를 놓아과 사건의 흐름이 있는지 확인하지만, 프록시의 도입 (선별, 이후 여론 조사로 시작)하지 않습니다 / O 이벤트. 하나 하나 설문 조사를 가지 않을 차단 된 스레드에서 어떤 이벤트가없는 경우.

while true  
{  
    select(streams[]) //这一步死在这里,直到有一个流有I/O事件时,才往下执行  
    for i in streams[]  
    {  
        if i has data  
        read until unavailable  
    }  
}

여기에 문제가 그냥 알고에서, 우리는 선택이 있습니다 I / O 이벤트가 발생하지만, 우리는 여전히 모든 미분화 흐름을 폴링 데이터를 읽을 식별하거나 기록 데이터를해야하는 스트림 모른다 그들이 운영하는, 흐름. 그래서 선택 폴링 미분화 복잡도 O (n)은 데. 더 많은 전류, 더 이상 미분화 폴링 이벤트입니다.

epoll에 바쁜 폴링 및 비 차별적 여론 조사에서 다른 이벤트 설문 조사, 흐름이 I / O 이벤트가 알려 어떻게 발생 epoll 파일로 이해 될 수있다. 그래서 실제로는 epoll 이벤트 중심 (디스크립터와 관련된 각 이벤트), 우리가이 스트림에서 운영하는이 시간은 의미가 만들어집니다. O 복잡성의 아래 (1).

while true  
{  
    active_stream[] = epoll_wait(epollfd)  
    for i in active_stream[]  
    {  
        read or write till  
    }  
}

가장 큰 차이는 그 선택과는 epoll 그래서,이 흐름 이벤트의 특정 수의 이벤트를 스트리밍으로하는에,있는 하나의 폴링에 의해 일을 가야했다 당신에게 단지 선택합니다. 사건은 epoll에이 사건이 발생하여, 특정 흐름을 찾을 수 있었다, 당신을 말할 것이다 일어났다. 좋은 성능의하지 조금도.

의 시스템 차단 프리미티브 (블록)에 의해 자동으로 수행됩니다 인해 같은 시스템 리소스 요구가 일부 작업이 완료를 위해, 새로운 데이터가 아직 등등에 도달하거나 새로운 일이 아니다 기다리고 실패로 기대하지 않았다 특정 이벤트의 발생에 프로세스를 실행 자신의 상태를 실행 상태에 의해 차단. 가시 차단 프로세스는 프로세스 자체가 활성 동작이며, 따라서 단지 동적 프로세스 (CPU를 얻기 위해)이 차단 된 상태로 전환 될 수있다 실행된다. 프로세스가 차단 된 상태가되면, CPU 자원을 차지하지 않습니다.

I / O 모델

입력 동작은 일반적으로 두 단계를 포함한다 :

  • 데이터를 기다리는 것은 너무,이 단계가 네트워크에서 도착하는 데이터에 관련되는 소켓 작업에 대한 준비, 그리고 그것은 커널 버퍼에 복사합니다.
  • 프로세스 버퍼를 커널 버퍼에서 데이터를 복사합니다.

내가 차단 / O 모델

가장 널리 사용되는 모델은 기본적으로 I에게 / O 모델을 차단하고, 모든 소켓이 차단됩니다. 프로세스 (시스템 호출이 중단되면 물론 반환) 반환을 처리하기 위해 데이터를 복사 할 때 전체 프로세스가 버퍼 때까지 차단의에 recvfrom 시스템 호출을 호출합니다.

IMG

I에게 비 차단 / O 모델 (NIO)

우리가 소켓을 넣을 때 내가 요청하는 경우 커널에게, 즉, 비 블록 / O 작업을 완료 할 수없는, 공정하지 않습니다 수면하지만 반환 오류. 데이터가 준비가되어 있지 않은 경우에는, 커널 반환 즉시 오류를 EWOULDBLOCK. 제 시간에 recvfrom를 호출 할 때, 데이터는 이미 그 후, 버퍼에 데이터를 복사하는 과정이 존재한다. 이것은 하나의 폴링 (폴링) 작업이 있습니다.

IMG

I / O 다중화 모델

이 모델의 사용은 이러한 두 가지 기능도 차단 과정을 선택하고 설문 조사 기능. 그러나,이 I 차단 / O가 다른, 두 가지 기능이 I / O 동작들의 다수의 블록을 동시에 할 수 있으며, 또한 판독 동작을 동시에 복수 I 상기 복수의 기록 동작 / O 기능을 시험하기위한 데이터가 판독 될 때까지 또는 쓰기.

select 被调用后,进程会被阻塞,内核监视所有 select 负责的 socket,当有任何一个 socket 的数据准备好了,select 就会返回套接字可读,我们就可以调用 recvfrom 处理数据。

阻塞 I/O 只能阻塞一个 I/O 操作,而 I/O 复用模型能够阻塞多个 I/O 操作,所以才叫多路复用。

IMG

异步I/O模型(AIO)

进程发起 read 操作之后,立刻就可以开始去做其他的事情。内核收到了一个 asynchronous read 之后,首先会立即返回,所以不会 block 任何用户进程。Kernel 会等待数据准备完成,然后将数据拷贝到用户内存,当这一切完成后,kernel 会给用户发送一个 singal,告诉它 read 操作完成了。

IMG

服务端编程经常需要构造高性能的 IO 模型,常见的 IO 模型有四种:

  • 同步阻塞 IO(Blocking IO):即传统的 IO 模型。
  • 异步非阻塞 IO(Non-blocking IO):默认创建的 socket 都是阻塞的,非阻塞 IO 要求 socket 被设置为 NONBLOCK。这里的 NIO 并未 Java 的 NIO(new IO)库。
  • IO 多路复用(IO Multiplexing):即经典的 Reactor 设计模式,Java 中的 Selector 和 Linux 中的 epoll 都是这种模型。
  • 异步 IO(Asynchronous IO):即经典的 Proactor 设计模式,也被称为异步非阻塞 IO。

同步阻塞 IO

同步阻塞 IO 模型是最简单的 IO 模型,用户线程在内核进行 IO 操作时被阻塞。

IMG

  1. 用户线程通过系统调用 read 发起 IO 读操作。由用户空间转到内核空间。
  2. 内核等数据包到达后,将接收到的数据拷贝到用户空间
{

read(socket, buffer); //一直阻塞等待
process(buffer);

}

用户需要等待 read 将 socket 中的数据读取到 buffer,才能继续处理接收的数据。整个 IO 请求的过程中,用户线程是被阻塞的,导致用户在发起 IO 请求时不能做任何事情,对 CPU 的资源利用率不够。

同步非阻塞 IO

同步非阻塞 IO 是在同步阻塞 IO 的基础上,将 socket 设置为 NONBLOCK。这样做用户线程可以在发起 IO 请求后立即返回。

IMG

  1. 由于 socket 是非阻塞的方式,因此用户线程发起 IO 请求时立即返回。
  2. 但并未读取到任何数据,用户线程需要不断地发起 IO 请求,直到数据到达后,才读取到真正的数据。
{

while(read(socket, buffer) != SUCCESS); //不断请求

process(buffer);

}
  1. 用户需要不断地调用 read,尝试读取 socket 中的数据,直到读取成功后,才继续处理接收的数据。
  2. 虽然可以立即返回,但是为了等到数据,仍需要不断地轮询,重复请求,消耗了大量的 CPU 资源。一般很少直接使用这种模型,而是在其他 IO 模型中使用非阻塞 IO 这一特性。

IO 多路复用

IO 多路复用模型是建立在内核提供的多路分离函数 select 基础之上的,使用 select 函数可以避免同步非阻塞 IO 模型中轮询等待的问题。

IMG

  1. 用户首先将需要进行 IO 操作的 socket 添加到 select 中,然后阻塞等待 select 系统调用返回。
  2. 当数据到达时,socket 被激活,select 函数返回。
  3. 用户线程正式发起 read 请求,读取数据并继续执行。

从流程上来看,使用 select 进行 IO 请求和同步阻塞模型并没有太大区别,甚至还加了监视 socket 的操作,效率好像更差。但是使用 select 后最大的优势是用户可以在一个线程内同时处理多个 socket 的 IO 请求。用户可以注册多个 socket,然后不断地调用 select 读取被激活的 socket,即可达到在同一个线程内同时处理多个 IO 请求的目的。在同步阻塞模型中,必须通过多线程才能达到这个目的。

{
    select(socket);
    while(1) {
        sockets = select();
        for(socket in sockets) {
        if(can_read(socket)) {
            read(socket, buffer);
            process(buffer);
        }
    }
    }
}

使用 select 函数的有点不仅限于此。虽然上述方式允许多线程内处理多个 IO 请求,但是每个 IO 请求的过程还是阻塞的(在 select 上阻塞),平局实现比同步阻塞 IO 模型还要长。如果用户线程只注册自己感兴趣的 socket 或者 IO 请求,然后去做自己的事情,这样可以提高 CPU 的利用率。

IO 多路复用模型使用了 Reactor 设计模式实现了这一个机制:

IMG

EventHandler 抽象类表示 IO 事件处理器,他拥有 IO 文件句柄 handle(通过 get_handle 获取),以及对 handle 的操作 handle_event(读/写等)。继承于 EventHandler 的子类可以对事件处理器的行为进行定制。Reactor 用于管理 EventHandler(注册,删除等),并使用 handle_events 实现事件循环,不断调用同步事件多路分离器的多路分离函数 select,只要某个文件句柄被激活(可读/写等),select 就返回,handle_events 就会调用与文件句柄相关联的事件处理器 handle_event 进行相关操作。

IMG

通过 Reactor 的方式,可以将用户线程轮询 IO 操作状态的工作统一交给 handle_events 事件循环进行处理。用户线程注册事件处理器之后可以继续执行其他的工作,而 Reactor 线程负责调用内核的 select 函数检查 socket 的状态。当有 socket 被激活时,则通知相应的用户线程(或执行用户线程的回调函数),执行 handle_event 进行数据读取,处理的工作。由于 select 函数是阻塞的,因此多路 IO 复用模型也被称为一步阻塞 IO 模型。这里说的阻塞指的是 select 函数执行的线程被阻塞,而不是 socket。

IO 多路复用模型的伪代码描述为:

void UserEventHandler::handle_event() {
    if(can_read(socket)) {
        read(socket, buffer);
        process(buffer);
    }
}
{
    Reactor.register(new UserEventHandler(socket));
}

用户需要重写 EventHandler 的 handle_event 函数进行读取数据,处理数据的工作,用户线程只需要将自己的 EventHandler 注册到 Reactor 即可。

Reactor 中的 handle_events 事件循环的伪代码如下:

Reactor::handle_events() {
        while(1) {
                sockets = select();
                for(socket in sockets) {
                        get_event_handler(socket).handle_event();
                }
        }
}

事件循环不断地调用 select 获取被激活的 socket,然后根据 socket 对应的 EventHandler,执行 handle_event 函数即可。

异步IO

真正的异步 IO 需要操作系统更强的支持。在 IO 多路复用模型中,事件循环将文件句柄的状态事件通知给用户线程,用户自行读取数据,处理数据。而在异步 IO 模型中,当用户线程收到通知时,数据已经被内核读取完毕,并且放到了用户线程指定的缓冲区内,内核在 IO 完成后通知用户线程直接使用即可。

异步 IO 模型使用了 Proactor 设计模式实现了这一机制。

IMG

Proactor 模式和 Reactor 模式在结构上比较相似,不过在用户使用方式上差别较大。Reactor 模式中,用户线程通过向 Reactor 对象注册感兴趣的事件监听,然后触发时调用事件处理函数。而 Proactor 模式中,用户线程将 AsynchronousOperation,Proactor 以及操作完成时的 CompeletionHandler 注册到 AsynchronousOperationProcessor。AsynchronousOperationProcessor 使用 Facade 模式提供了一组异步操作 API 供用户使用,当用户线程调用异步 API 后,便继续执行自己的任务。

Proactor 负责回调每一个异步操作的事件完成处理函数 handle_event,虽然 Proactor 模式中每个异步操作都可以绑定一个 Proactor 对象,但是一般在操作系统中,Proactor 被实现为 Singleton 模式,以便集中化分发操作完成事件。

IMG

  1. 직접 커널에서 제공하는 비동기 IO API를 사용하여 사용자가 읽기 요청을 시작하고, 시작하는 즉시 스레드 돌아갑니다.
  2. 사용자 스레드는 AsynchronousOperation라고하고 CompletionHandler는 IO 작업을 처리하기 위해 별도의 커널 스레드에서, 커널에 다음 운영 체제를 등록했다.
  3. 요청 된 읽을 데이터가 도착하면, 커널에 의해 읽을 데이터가 소켓에 대한 책임, 사용자 지정된 버퍼를 기록합니다.
  4. 커널은 데이터를 읽어와 사용자 스레드는 CompletionHandler는 Proactor, Proactor 내부 사용자 스레드, 완전한 비동기 IO의 IO 완료 통지 정보를 배포 등록.
void UserCompletionHandler::handle_event(buffer) {
    process(buffer);
}

{
    aio_read(socket, new UserCompletionHandler);
}

비동기 IO는 일반적으로 기본적인 요구를 충족하기 위해 처리 IO 다중화 아키텍처 모델 + 멀티 스레드를 많이 사용하는 고성능 서비스를 동시에 사용되지 않습니다. 자바 7 후 비동기 IO 지원합니다.

추천

출처www.cnblogs.com/paulwang92115/p/12186174.html