💋 인트로
프로그래밍에서의 입출력(I/O) 작업은 다양한 접근 방식을 통해 처리됩니다.
이 포스팅에서는 Block vs. Non-Block, 그리고 Sync vs. Async의 다양한 I/O 처리 방식을 알아보고, 각각의 특징과 사용 사례에 대해 알아보겠습니다.
💋 Blocking I/O VS Non-Blocking I/O
⇒ 다른 주체가 작업할 때 물리적
으로 자신의 제어권이 있는지가 기준입니다. (물리적인 실행 특성)
✔️ Blocking I/O
- 자신의 작업을 진행하다가 다른 주체의 작업이 시작되면, 다른 작업이 끝날 때까지 기다렸다가 자신의 작업을 시작하는 것
물리적
으로 I/O 작업이 끝날 때까지 다른 작업을 하지 않고 기다리는 것을 의미합니다.
✔️ Non-Blocking I/O
- 다른 주체의 작업에 관련없이 자신의 작업을 하는 것
- I/O 작업이 진행되는 동안에도
물리적
으로 대기하지 않고, 작업을 수행할 수 있습니다.
💋 Synchronous VS Asynchronous
⇒ 논리적
으로 I/O 작업의 결과에 관심이 있는지 아닌지가 기준입니다. (코드의 논리적인 구조)
✔️ Synchronous
논리적
으로 I/O 작업으로부터 돌아올 결과를 기다리고, 처리를 합니다.- I/O 작업의 결과에 따라 처리해야 하므로, 순서는 자연스럽게 한 I/O 작업 이후 하던 작업을 이어가는 형태로 진행됩니다.
✔️ Asynchronous
논리적
으로 I/O 작업이 일어나는 동안 다른 작업을 수행할 수 있습니다.- 주로 코드의 논리적인 흐름을 나타냅니다.
- 돌아올 결과를 기다리지 않고 일을 하며, 돌아온 결과에 대해 처리를 할 수도 있고 안할 수도 있습니다.
- 보통 비동기 I/O 작업이 완료되면 알 수 있는 콜백(callback) 함수나 이벤트 핸들러를 등록해 결과를 처리합니다.
💋 IBM의 I/O 처리 방식 분류
2006년에 나와서 화제가 되었다는 글인데(저는 그때 6살이었네요…><) < />>
위에서 본 것처럼 Blocking, Non-Blocking / Synchronous, Asynchronous 이렇게 두 가지 서로 대응되는 개념의 쌍을 학습했고, 두 가지는 서로 뭔가 아리까리하지만 확실히 다른 기준이라는 것을 확인했습니다.
그러면 이제 이렇게 4가지 조합이 나올텐데요!
각 조합별로 살펴봅시다.
💋 Synchronous blocking I/O
- Blocking ⇒ 제어권이 없으므로, I/O 작업이 끝날 때까지 스레드에서 다른 작업을 하지 못합니다.
- Synchronous ⇒ 다른 작업으로부터 결과 반환 시 곧바로 처리합니다.
✔️ read/write
표에서 이 방식의 예시로 read/write를 적어 놨는데, 이 방식은 특히 read와 write와 같은 기본적인 입출력 작업에서 많이 사용됩니다. 입출력이 완료되기 전까지 대기하며, 작업이 완료되면 결과를 반환하거나 다음 단계로 진행한다는 특성을 반영합니다.
✔️ 실습 코드
위와 같은 예시로는 자바의 입력 요청이 있습니다.
public class IOTest {
public static void main(String[] args) {
System.out.print("입력: ");
final Scanner scanner = new Scanner(System.in);
final String input = scanner.nextLine();
System.out.println("Blocking / Synchronous");
System.out.println("input = " + input);
}
}
스레드의 제어권이 넘어가서, 입력 작업이 일어날 동안 아무 일도 하지 않은 채 대기합니다.
또한 입력이 일어나면, 곧바로 뒤에 이어서 그에 대한 처리를 하게 됩니다.
💋 Synchronous non-Blocking I/O
- Non-Blocking ⇒ 제어권은 잃지 않기 때문에 I/O 작업이 끝나기를 기다리지 않고, 스레드에서 다른 작업을 대기 없이 계속 할 수 있습니다.
- Synchronous ⇒ 결과를 처리하기 위해 관심을 갖기 때문에, 결과가 나왔는지 일정한 시간 간격으로 물어보고, 결과를 받으면 곧바로 업무를 처리합니다.
✔️ 구현 방식: polling, interrupt
Non-Blocking I/O이기 때문에 I/O 결과가 곧바로 반환되지는 않습니다. 보통 이렇게 요청한 쪽에서 주기적으로 I/O 작업이 완료되었는지 계속 확인해야 하는데, 이런 방식을 polling
이라고 합니다. polling은 주기적으로 특정 상태나 이벤트를 확인하는 방법을 나타내며, 이를 통해 I/O 작업의 완료 여부를 주기적으로 체크합니다. 또는, I/O가 완료된 경우 상태가 변경되었을 때 interrupt
를 사용하여 알림을 받는 방식으로도 구현할 수 있습니다.
✔️ 단점
또 위 방법에서 보시면, 커널이 I/O 작업으로부터 Read response를 받는 시점과 애플리케이션이 data movement를 받는 시점 사이에 time gap이 있는 것을 확인할 수 있습니다. 이 time gap을 통해 해당 방식이 I/O의 지연을 유발한다는 것을 확인할 수 있습니다. ⇒ 비효율적입니다!
✔️ 실습 코드
아래 코드로 실습해볼 수 있습니다.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class NonBlockingSynchronousExample {
public static void main(String[] args) {
System.out.println("Non-Blocking / Synchronous");
// BufferedReader를 사용하여 입력을 동기적으로 읽어들임
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
try {
System.out.print("입력: ");
// 입력이 준비될 때까지 블로킹되지만, 다른 작업은 수행 가능
while (!reader.ready()) {
System.out.println("다른 작업을 수행 중...");
Thread.sleep(1000);
}
// 동기적으로 입력을 읽고 결과를 처리
String input = reader.readLine();
System.out.println("입력 받은 값: " + input);
// 나머지 작업 수행
System.out.println("나머지 작업을 수행 중...");
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}
입력이 일어날 때까지 마냥 대기하는 것이 아니라, non-blocking 방식이기 때문에 이어서 다른 작업을 수행하고, synchronous하기 때문에 입력값이 돌아오면 곧바로 입력값에 대한 처리를 합니다.
✔️ read/write O_NONBLOCK
일반적으로 Non-blocking
과 Synchronous
은 서로 상충되는 특성을 가지고 있어 함께 사용되기 어렵지만, 상황에 따라 의미가 조금 달라질 수 있습니다.
참고로, 위 표에 적힌 read/write O_NONBLOCK
에 대해 조금만 설명을 하자면, 이 옵션은 파일 디스크립터를 Non-blocking 모드로 설정하는 옵션입니다. 예를 들어, C 언어의 "open" 함수를 사용하여 파일을 열 때 "O_NONBLOCK" 플래그를 사용할 수 있습니다.
int fd = open("example.txt", O_RDONLY | O_NONBLOCK);
이렇게 하면 파일 디스크립터 fd
가 Non-blocking 모드로 열리게 됩니다. 따라서 이 파일 디스크립터를 사용하여 read나 write 작업을 수행할 때, I/O 작업이 완료되지 않았더라도 대기하지 않고 즉시 반환됩니다. 이는 Non-blocking 특성을 갖는 것으로 볼 수 있습니다.
💋 Asynchronous blocking I/O
- Blocking ⇒ 제어권은 없으므로, I/O가 끝나기만을 기다리면서 다른 작업을 할 수 없습니다.
- Asynchronous ⇒ 결과에 대한 처리는 할 수도 안할 수도 있습니다.
I/O 작업 결과에 대한 처리를 곧바로 할 것도 아닌데 뭐하러 계속 I/O 작업이 끝나기를 기다릴까요? 굉장히 궁합이 안맞고 비효율적인 것 같은데요!
실제로도 해당 방법은 자주 사용하지는 않고, 프로그래머의 실수로 많이 일어난다고 합니다.
✔️ I/O multiplexing
- 하나의 스레드가 여러 개의 입출력 소켓을 감시하면서, 어떤 소켓에서 I/O 작업이 완료되었는지 감시하는 기술
- 다중 클라이언트와 효율적으로 상호작용할 수 있도록 도와줍니다.
- 여러 소켓이 동시에 이벤트가 발생할 때까지 블로킹되지 않고 대기
- 여러 소켓에 대한 I/O 이벤트를 단일 스레드에서 처리할 수 있습니다.
select
또는poll
과 같은 함수를 사용하여 여러 소켓에서 I/O 이벤트를 감시하고, 이벤트가 발생한 소켓들에 대해서만 작업을 수행합니다.다수의 클라이언트 연결을 단일 스레드로
효과적으로 다룰 수 있어서, 많은 서버 프로그램에서 사용되는 기술
💋 Asynchronous non-Blocking I/O (AIO)
- Non-Blocking ⇒ I/O 작업이 일하는 동안 스레드는 기다리지 않고 다른 작업을 합니다.
- Asynchronous ⇒ 결과에 대한 처리는 할 수도 안할 수도 있습니다.
이 방식에서 일반적으로 I/O 작업 요청은 즉시 반환되어 I/O 작업이 성공적으로 시작되었음을 알려주고, 프로그램은 I/O 작업이 완료될 동안 다른 처리를 수행할 수 있습니다. I/O 결과가 도착하면 시그널이나 스레드 기반의 콜백을 생성하여 I/O 결과를 처리할 수 있습니다.
✔️ API callback in JS
이 방식은 자바스크립트의 API 요청에서 callback 함수를 사용하는 경우를 생각해보면 쉽게 이해할 수 있습니다.
app.post('/call', function(request, response){
var json = request.body;
var p1 = json.p1;
var p2 = json.p2;
var p3 = json.p3;
var p4 = json.p4;
call.callApi(p1, p2, p3, p4, function(err, result) { // ***
if (err) { // ***
// Send error response // ***
// ... // ***
} else { // ***
// Send successful response // ***
response.send(result); // ***
} // ***
});
});
✔️ 실습 코드
자바 코드를 통해 실습해 보겠습니다.
package kitchenpos.application;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class NonBlockingAsyncExample {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// CompletableFuture를 사용하여 비동기적으로 작업을 수행
CompletableFuture<String> futureResult = CompletableFuture.supplyAsync(() -> performAsyncTask());
// 다른 작업을 수행할 수 있음
System.out.println("다른 작업을 수행 중...");
// 비동기 작업이 완료될 때까지 대기하지 않고 다른 작업을 계속 수행
for (int i = 0; i < 10; i++) {
Thread.sleep(500);
System.out.println("비동기 I/O가 일어나는 동안 I/O 결과와 무관하게 작업합니다.");
}
// 비동기 작업의 결과를 얻어옴
String result = futureResult.get();
System.out.println("비동기 작업의 결과: " + result); // 비동기 I/O 결과를 처리할 수도 있고, 처리하지 않을 수도 있음.
}
private static String performAsyncTask() {
// 비동기적으로 수행될 작업
try {
Thread.sleep(3000); // 예시를 위해 3초 동안 대기
} catch (InterruptedException e) {
e.printStackTrace();
}
return "비동기 작업 완료!";
}
}
💋 참고자료
- https://www.baeldung.com/java-io-vs-nio
- https://www.geeksforgeeks.org/blocking-and-nonblocking-io-in-operating-system/
- https://developer.ibm.com/articles/l-async/
- https://www.geeksforgeeks.org/difference-between-asynchronous-and-non-blocking/
- https://www.youtube.com/watch?v=XNGfl3sfErc&t=236s
- https://www.youtube.com/watch?v=oEIoqGd-Sns
도움이 되었다면, 공감/댓글을 달아주면 깃짱에게 큰 힘이 됩니다!🌟
비밀댓글과 메일을 통해 오는 개인적인 질문은 받지 않고 있습니다. 꼭 공개댓글로 남겨주세요!
'Computer Science > Operating System' 카테고리의 다른 글
[OS] 운영체제(Operating System)가 뭘까? 왜 필요하고, 무슨 역할을 하는지 알아보자! (0) | 2024.04.10 |
---|---|
[OS] MacOS(m1/m2) ARM64 아키텍처에서 Virtualbox 설치하기 (0) | 2024.03.06 |
[OS] System call, System interrupt을 통해 유저 모드에서 커널 모드를 실행할 수 있다! (0) | 2023.11.25 |
[OS] CPU Scheduler: 운영체제가 CPU에게 일 시키는 방법(Dispatcher, 비선점 VS 선점 스케줄링, 스케줄링 알고리즘) (0) | 2023.11.24 |
[OS] OS, Java에서 프로세스의 상태 (State of Process in OS and Java) (0) | 2023.11.23 |