아마도 많은 친구들이 NIO를 배울 때 약간 어렵다고 느낄 것이고, NIO의 많은 개념은 명확하지 않습니다. Java NIO 프로그래밍을 시작하기 전에 오늘 몇 가지 기본 지식인 I/O 모델에 대해 논의해 보겠습니다. 다음 기사는 동기화와 비동기의 개념으로 시작한 다음 차단과 비차단의 차이점을 설명하고 차단 IO와 비차단 IO의 차이점을 소개한 다음 동기 IO와 비동기 IO의 차이점을 소개하고 다음을 소개합니다. 5가지 IO 모델, 그리고 마침내 고성능 IO 설계와 관련된 두 가지 설계 모델(Reactor와 Proactor)을 소개합니다
다음은 이 글의 목차 개요입니다.
1. 동기화란 무엇인가요? 비동기식이란 무엇입니까?
2. 차단이란 무엇인가요? 논블로킹이란 무엇인가요?
3. 블로킹IO란? 논블로킹 IO란 무엇입니까?
4. 동기식 IO란 무엇인가요? 비동기 IO란 무엇입니까?
5. 5가지 IO 모델
6. 2가지 고성능 IO 설계 패턴
1. 동기화란? 비동기식이란 무엇입니까?
동기화와 비동기화의 개념은 오래 전부터 존재해왔고, 인터넷에는 동기화와 비동기화에 대한 많은 의견이 있습니다. 다음은 개인적인 이해입니다.
동기화란 여러 작업이나 이벤트가 발생할 경우 이러한 작업이나 이벤트를 하나씩 수행해야 한다는 의미입니다. 하나의 이벤트나 작업을 실행하면 전체 프로세스가 일시적으로 대기하게 됩니다. 이러한 이벤트가 동시에 실행될 수 있는 방법은 없습니다.
비동기식이란 여러 작업이나 이벤트가 발생하는 경우 이러한 이벤트가 동시에 실행될 수 있으며 하나의 이벤트나 작업 실행으로 인해 전체 프로세스가 일시적으로 기다리지 않는다는 의미입니다.
이것은 동기식과 비동기식입니다. 간단한 예를 들자면, 두 개의 하위 작업 A와 B를 포함하는 작업이 있는 경우 A가 실행 중일 때 B는 A가 완료될 때까지만 기다린 후 B를 실행할 수 있습니다. 비동기의 경우 A와 B를 실행할 수 있습니다. 동시에 B는 A의 실행이 완료될 때까지 기다릴 필요가 없으므로 A의 실행으로 인해 전체 작업이 일시적으로 대기하지 않습니다.
그래도 이해가 되지 않는다면 다음 두 가지 코드를 먼저 읽어보세요.
void fun1() { } void fun2() { } void function(){ fun1(); fun2() ..... ..... }
이 코드는 메소드 함수에서 fun1을 실행하면 후속 fun2를 사용할 수 없게 됩니다. 실행하려면 fun2는 fun1을 기다려야 합니다. 실행이 완료된 후에만 실행할 수 있습니다.
그런 다음 다음 코드를 살펴보세요.
void fun1() { } void fun2() { } void function(){ new Thread(){ public void run() { fun1(); } }.start(); new Thread(){ public void run() { fun2(); } }.start(); ..... ..... }
이 코드는 일반적인 비동기식입니다. fun1의 실행은 fun2의 실행에 영향을 미치지 않으며 fun1 및 fun2의 실행으로 인해 후속 실행 프로세스가 일시적으로 발생하지 않습니다. .
실제로 동기화와 비동기성은 매우 광범위한 개념입니다. 이들의 초점은 여러 작업과 이벤트가 발생할 때 이벤트 발생 또는 실행으로 인해 전체 프로세스가 일시적으로 대기 하는지 여부에 있습니다. Java의 동기화 키워드를 사용하면 동기화와 비동기성을 비유할 수 있다고 생각합니다. 여러 스레드가 동시에 변수에 액세스하는 경우 해당 변수에 대한 각 스레드의 액세스는 이벤트입니다. 동기화를 위해서는 한 스레드가 변수에 액세스하는 동안 다른 스레드가 기다려야 합니다. , 여러 스레드가 변수에 하나씩 액세스할 필요는 없지만 동시에 액세스할 수 있습니다.
그래서 저는 개인적으로 동기화와 비동기를 여러 가지로 표현할 수 있다고 생각하는데, 기억해야 할 핵심은 여러 작업과 이벤트가 발생할 때, 이벤트의 발생이나 실행으로 인해 전체 프로세스가 일시적으로 대기하게 되는지 여부입니다. 일반적으로 비동기는 멀티 스레딩을 통해 달성할 수 있지만 멀티 스레딩을 비동기와 동일시하지 마십시오. 비동기는 단지 매크로 패턴일 뿐이며 멀티 스레딩을 사용하여 비동기를 달성하는 것은 단지 수단일 뿐이며 비동기 구현도 달성할 수 있습니다. 다중 처리를 통해.
2. 방해란 무엇인가요? 논블로킹이란 무엇인가요?
동기화와 비동기화의 차이점은 앞서 소개했습니다. 이번 섹션에서는 차단과 비차단의 차이점을 살펴보겠습니다.
Blocking은 : 이벤트나 작업이 실행될 때 요청 작업을 실행하지만 요청 작업에 필요한 조건이 충족되지 않기 때문에 조건이 충족될 때까지 대기합니다.
차단 수단 : 이벤트나 작업이 실행 중일 때 요청 작업에 필요한 조건이 충족되지 않으면 조건이 충족되지 않았음을 알리는 플래그 메시지가 즉시 반환되며, 거기서 영원히 기다리지 마세요.
이것이 차단과 비차단의 차이입니다. 즉, 차단과 비차단의 주요 차이점은 작업 요청 시 조건이 충족되지 않으면 영원히 기다리거나 플래그 메시지를 반환할지에 있습니다.
간단한 예:
파일의 내용을 읽고 싶은 경우 현재 파일에 읽을 수 있는 내용이 없으면 동기화를 위해 파일에 읽을 수 있는 내용이 있을 때까지 기다리게 됩니다. -blocking, 읽을 파일에 내용이 없음을 알리기 위해 플래그 메시지가 직접 반환됩니다.
인터넷의 일부 친구들은 동기화와 비동기를 각각 차단 및 비차단과 동일시합니다. 실제로는 완전히 다른 두 가지 개념입니다. 후속 IO 모델을 이해하려면 이 두 가지 개념 집합 간의 차이점을 이해하는 것이 매우 중요합니다.
동기화 및 비동기화의 초점은 하나의 작업 실행으로 인해 여러 작업이 실행되는 동안 전체 프로세스가 일시적으로 대기하게 되는지 여부입니다.
차단 및 비차단의 초점은 조건이 충족되는 경우 요청 작업을 실행할 때입니다. 해당 작업에 대한 조건이 충족되지 않으면 조건이 충족되지 않았음을 알리는 플래그 메시지가 반환됩니다.
블로킹과 비블로킹의 이해는 스레드 블로킹에 비유하여 이해할 수 있습니다. 스레드가 요청 작업을 수행할 때 조건이 충족되지 않으면 차단됩니다. 즉, 조건이 충족될 때까지 기다립니다.
3. Blocking I/O란 무엇인가요? Non Blocking I/O란 무엇인가요?
Blocking IO와 Non-Blocking IO를 이해하기 전에 먼저 구체적인 IO 작업 프로세스가 어떻게 수행되는지 살펴보겠습니다.
일반적으로 IO 작업에는 하드 디스크 읽기 및 쓰기, 소켓 읽기 및 쓰기, 주변 장치 읽기 및 쓰기가 포함됩니다.
사용자 스레드가 IO 요청 작업을 시작하면(이 문서에서는 읽기 요청 작업을 예로 들었습니다) 커널은 읽을 데이터가 준비되었는지 확인합니다. IO 차단의 경우 데이터가 준비되지 않은 경우 데이터가 준비될 때까지 기다리세요. 비차단 IO의 경우 데이터가 준비되지 않은 경우 현재 읽고 있는 데이터가 준비되지 않았음을 사용자 스레드에 알리는 플래그 메시지가 반환됩니다. 데이터가 준비되면 데이터가 사용자 스레드에 복사되어 전체 IO 읽기 요청 작업이 완료됩니다. 즉, 전체 IO 읽기 요청 작업에는 두 단계가 포함됩니다.
1) 데이터가 준비되었는지 확인합니다.
2) 데이터 복사(커널이 데이터를 사용자 스레드에 복사합니다).
그러면 블로킹(blocking IO)과 비차단(non-blocking IO)의 차이점은 첫 번째 단계에서 데이터가 준비되지 않은 경우 데이터가 준비되었는지 확인하는 과정에서 항상 기다려야 하는지 여부입니다. 준비 또는 플래그 정보를 직접 반환합니다.
Java의 기존 IO는 소켓을 통해 데이터를 읽는 등의 IO를 차단합니다. read() 메서드를 호출한 후 데이터가 준비되지 않은 경우 현재 스레드는 항상 읽기 메서드 호출에서 차단되고 그 때까지 반환되지 않습니다. 데이터가 있고 비차단 IO인 경우 데이터가 준비되지 않은 경우 read() 메서드는 항상 대기하는 대신 현재 스레드에 데이터가 준비되지 않았음을 알리는 플래그 정보를 반환해야 합니다.
4. 동기 I/O란 무엇인가요?
먼저 동기 IO와 비동기 IO의 정의를 살펴보겠습니다. "유닉스 네트워크 프로그래밍" 책에 나오는 동기 IO와 비동기 IO에 대한 정의는 다음과 같습니다.
동기 I/O 작업 I/O 작업이 완료될 때까지 요청 프로세스가 차단됩니다.<code>A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes.<br/> An asynchronous I/O operation does not cause the requesting process to be blocked.
비동기 I/O 작업은 요청 프로세스가 차단되지 않습니다.
문자 그대로의 의미에서 볼 수 있습니다. 동기 IO는 if a 스레드가 IO 작업을 요청하면 IO 작업이 완료되기 전에 스레드가 차단됩니다.
비동기 IO는 스레드가 IO 작업을 요청하면 IO 작업으로 인해 요청 스레드가 차단되지 않음을 의미합니다.
실제로 동기식 IO 및 비동기식 IO 모델은 사용자 스레드와 커널 간의 상호 작용을 목표로 합니다.
동기식 IO의 경우: 사용자가 IO 요청 작업을 실행한 후 데이터가 준비되지 않은 경우 사용자 스레드 또는 커널에 의해 지속적으로 처리됩니다. 데이터가 준비되었는지 폴링하기 위해 데이터가 준비되면 커널에서 사용자 스레드로 데이터를 복사합니다.
비동기 IO: IO 요청 작업의 발행만 수행됩니다. 사용자 스레드와 IO 작업의 두 단계는 커널에 의해 자동으로 완료된 다음 사용자에게 스레드 IO 작업이 완료되었음을 알리는 알림이 전송됩니다. 즉, 비동기 IO에서는 사용자 스레드가 차단되지 않습니다.
이것이 동기 IO와 비동기 IO의 주요 차이점입니다. 동기 IO와 비동기 IO의 주요 차이점은 데이터 복사 단계가 사용자 스레드 또는 커널에 의해 완료되는지 여부에 반영됩니다. 따라서 비동기 IO에는 운영 체제의 기본 지원이 있어야 합니다.
동기식 IO와 비동기식 IO는 차단 IO 및 비차단 IO와는 다른 개념입니다. 블로킹 IO와 비블로킹 IO는 사용자가 IO 작업을 요청할 때 데이터가 준비되지 않은 경우 데이터가 준비될 때까지 기다리는 경우 사용자 스레드가 여전히 플래그 메시지를 수신한다는 사실에 반영됩니다. 즉, Blocking IO와 Non-Blocking IO는 IO 작업의 첫 번째 단계에 반영되어 데이터 준비 여부를 확인할 때 처리됩니다.다섯 가지 I/O 모델
1. 차단 IO 모델
가장 전통적인 IO 모델, 즉 데이터를 읽고 쓰는 과정에서 차단이 발생합니다.当用户线程发出IO请求之后,内核会去查看数据是否就绪,如果没有就绪就会等待数据就绪,而用户线程就会处于阻塞状态,用户线程交出CPU。当数据就绪之后,内核会将数据拷贝到用户线程,并返回结果给用户线程,用户线程才解除block状态。
典型的阻塞IO模型的例子为:
data = socket.read();
如果数据没有就绪,就会一直阻塞在read方法。
2.非阻塞IO模型
当用户线程发起一个read操作后,并不需要等待,而是马上就得到了一个结果。如果结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦内核中的数据准备好了,并且又再次收到了用户线程的请求,那么它马上就将数据拷贝到了用户线程,然后返回。
所以事实上,在非阻塞IO模型中,用户线程需要不断地询问内核数据是否就绪,也就说非阻塞IO不会交出CPU,而会一直占用CPU。
典型的非阻塞IO模型一般如下:
while(true){ data = socket.read(); if(data!= error){ 处理数据 break; } }
但是对于非阻塞IO就有一个非常严重的问题,在while循环中需要不断地去询问内核数据是否就绪,这样会导致CPU占用率非常高,因此一般情况下很少使用while循环这种方式来读取数据。
3.多路复用IO模型
多路复用IO模型是目前使用得比较多的模型。Java NIO实际上就是多路复用IO。
在多路复用IO模型中,会有一个线程不断去轮询多个socket的状态,只有当socket真正有读写事件时,才真正调用实际的IO读写操作。因为在多路复用IO模型中,只需要使用一个线程就可以管理多个socket,系统不需要建立新的进程或者线程,也不必维护这些线程和进程,并且只有在真正有socket读写事件进行时,才会使用IO资源,所以它大大减少了资源占用。
在Java NIO中,是通过selector.select()去查询每个通道是否有到达事件,如果没有事件,则一直阻塞在那里,因此这种方式会导致用户线程的阻塞。
也许有朋友会说,我可以采用 多线程+ 阻塞IO 达到类似的效果,但是由于在多线程 + 阻塞IO 中,每个socket对应一个线程,这样会造成很大的资源占用,并且尤其是对于长连接来说,线程的资源一直不会释放,如果后面陆续有很多连接的话,就会造成性能上的瓶颈。
而多路复用IO模式,通过一个线程就可以管理多个socket,只有当socket真正有读写事件发生才会占用资源来进行实际的读写操作。因此,多路复用IO比较适合连接数比较多的情况。
另外多路复用IO为何比非阻塞IO模型的效率高是因为在非阻塞IO中,不断地询问socket状态时通过用户线程去进行的,而在多路复用IO中,轮询每个socket状态是内核在进行的,这个效率要比用户线程要高的多。
不过要注意的是,多路复用IO模型是通过轮询的方式来检测是否有事件到达,并且对到达的事件逐一进行响应。因此对于多路复用IO模型来说,一旦事件响应体很大,那么就会导致后续的事件迟迟得不到处理,并且会影响新的事件轮询。
4.信号驱动IO模型
在信号驱动IO模型中,当用户线程发起一个IO请求操作,会给对应的socket注册一个信号函数,然后用户线程会继续执行,当内核数据就绪时会发送一个信号给用户线程,用户线程接收到信号之后,便在信号函数中调用IO读写操作来进行实际的IO请求操作。
5.异步IO模型
异步IO模型才是最理想的IO模型,在异步IO模型中,当用户线程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从内核的角度,当它受到一个asynchronous read之后,它会立刻返回,说明read请求已经成功发起了,因此不会对用户线程产生任何block。然后,内核会等待数据准备完成,然后将数据拷贝到用户线程,当这一切都完成之后,内核会给用户线程发送一个信号,告诉它read操作完成了。也就说用户线程完全不需要实际的整个IO操作是如何进行的,只需要先发起一个请求,当接收内核返回的成功信号时表示IO操作已经完成,可以直接去使用数据了。
즉, 비동기식 IO 모델에서는 IO 작업의 어느 단계도 사용자 스레드를 차단하지 않습니다. 두 단계 모두 커널에 의해 자동으로 완료된 다음 사용자 스레드에 작업이 완료되었음을 알리는 신호가 전송됩니다. . 특정 읽기 및 쓰기를 위해 사용자 스레드에서 IO 함수를 다시 호출할 필요가 없습니다. 이는 신호 기반 모델과 다릅니다. 신호 기반 모델에서는 사용자 스레드가 신호를 수신하면 데이터가 준비되었음을 나타내며 사용자 스레드는 실제 읽기를 수행하기 위해 IO 함수를 호출해야 합니다. 쓰기 작업; 비동기 IO 모델에서 신호를 수신하면 IO 작업이 완료되었음을 나타내며 실제 읽기 및 쓰기 작업을 위해 사용자 스레드에서 IO 함수를 호출할 필요가 없습니다.
비동기 IO에는 운영 체제의 기본 지원이 필요합니다. Java 7에서는 비동기 IO가 제공됩니다.
처음 4개의 IO 모델은 실제로 동기식 IO이고 마지막 모델만 진정한 비동기식 IO입니다. 왜냐하면 다중화 IO이든 신호 구동 모델이든 IO 작업의 두 번째 단계에서 사용자 스레드 차단이 발생하기 때문입니다. 커널에 의한 데이터 복사 프로세스는 사용자 스레드를 차단합니다.
6. 두 가지 고성능 I/O 디자인 패턴
전통적인 네트워크 서비스 디자인 패턴에는 두 가지 고전적인 패턴이 더 있습니다.
하나는 멀티스레딩이고 다른 하나는 스레드 풀입니다.
멀티 스레드 모드의 경우, 즉 클라이언트가 오면 서버는 아래 그림과 같이 클라이언트의 읽기 및 쓰기 이벤트를 처리하기 위해 새 스레드를 생성합니다.
하지만 이 모드는 서버가 스레드를 사용하여 각 클라이언트 연결을 처리하기 때문에 처리가 간단하고 편리하며, 이는 많은 리소스를 소비합니다. 따라서 연결 수가 상한에 도달한 후 다른 사용자가 연결을 요청하게 되면 리소스 병목현상이 직접적으로 발생하게 되고, 심한 경우에는 서버가 다운되는 직접적인 원인이 될 수 있습니다.
따라서 one-thread-one-client 모델로 인해 발생하는 문제를 해결하기 위해 고정된 크기의 스레드 풀을 생성하고 클라이언트가 오면 스레드 풀 중 하나를 가져오는 스레드 풀 방식이 제안됩니다. 스레드 풀은 클라이언트가 읽기 및 쓰기 작업을 완료하면 스레드 점유를 넘겨주는 데 사용됩니다. 따라서 클라이언트별 스레드 생성으로 인한 리소스 낭비를 방지하여 스레드를 재사용할 수 있습니다.
그러나 스레드 풀에도 단점이 있습니다. 대부분의 연결이 긴 연결인 경우 스레드 풀의 모든 스레드가 일정 시간 동안 점유될 수 있습니다. 그런 다음 다른 사용자가 연결을 요청하면 문제가 발생할 수 있습니다. 사용 가능한 유휴 스레드가 없으면 클라이언트 연결이 실패하여 사용자 경험에 영향을 미칩니다. 따라서 스레드 풀은 다수의 짧은 연결 애플리케이션에 더 적합합니다.
따라서 다음과 같은 두 가지 고성능 IO 설계 패턴이 등장했습니다. Reactor
和Proactor
.
Reactor 모드에서는 각 클라이언트별로 관심 있는 이벤트를 먼저 등록한 후 이벤트가 발생하면 스레드가 각 클라이언트를 폴링하여 이벤트가 모두 완료되면 각 이벤트를 순차적으로 처리합니다. 처리되면 다음 그림과 같이 폴링을 계속하기 위해 전송됩니다.
여기에서 볼 수 있듯이 위의 5개 IO 모델의 다중화 IO는 Reactor 모드를 사용합니다. 위 그림에서는 각 이벤트가 순차적으로 처리되는 것을 보여주고 있습니다. 물론 이벤트 처리 속도를 높이기 위해 멀티 스레드나 스레드 풀을 통해 이벤트를 처리할 수도 있습니다.
Proactor 모드에서는 이벤트가 감지되면 새로운 비동기 작업이 시작된 다음 처리를 위해 커널 스레드로 전달됩니다. 커널 스레드가 IO 작업을 완료하면 작업이 완료되었음을 알리는 알림이 전송됩니다. 비동기 IO 모델은 Proactor 모드를 사용한다는 것을 알 수 있습니다.
위 내용에 오류가 있어도 양해해 주시고, 지적과 정정을 환영합니다!
더 많은 관련 콘텐츠를 알고 싶다면 PHP 중국어 웹사이트를 방문하세요: JAVA 비디오 튜토리얼
위 내용은 JAVA의 I/O 모델에 대한 자세한 설명(예제 포함)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!