아래 예제는 Windows에서 Overlapped I/O(비동기 I/O)를 사용해 파일에 동시에 여러 번의 쓰기 요청을 시도하고, 각각의 요청 완료를 이벤트를 통해 기다리는 구조입니다. 일반적으로 ‘fopen‘같은C라이브러리함수`fopen` 같은 C 라이브러리 함수는 비동기 처리를 지원하지 않으므로, 이 방식에서는 Win32 API의 CreateFile과 WriteFile을 사용합니다.
1. Overlapped I/O 기초 개념
- Overlapped 구조체
- 비동기 I/O 요청 시, I/O 작업 상태와 결과를 운영체제가 보관할 공간.
- OVERLAPPED 안에는 Offset(파일 쓰기 시작 위치), hEvent(이벤트 핸들) 등이 있음.
- 비동기(Overlapped) Write
- WriteFile 함수를 호출해도 즉시 반환.
- 실제 디스크 쓰기는 운영체제(커널)가 백그라운드에서 처리.
- 완료 시, OVERLAPPED.hEvent에 연결된 이벤트가 SET됨.
- 이벤트 객체 (Event)
- 비동기 I/O 완료 시점을 알려줌.
- CreateEvent로 생성 → WriteFile 시점에 OVERLAPPED.hEvent에 연결 → 완료 시 OS가 SET.
- WaitForMultipleObjects
- 여러 이벤트 핸들을 기다리는 함수.
- 이벤트가 하나라도 SET 상태가 되면 반환(또는 모두 기다리는 모드).
- 해당 인덱스로 어떤 I/O가 완료되었는지 알 수 있음.
2. 코드 흐름 요약
// 1. 비동기 파일 열기
HANDLE hFile = ::CreateFile(
_T("TestFile.txt"),
GENERIC_WRITE, // 쓰기 모드
0, // 공유하지 않음
NULL,
CREATE_ALWAYS, // 파일이 이미 있으면 덮어쓰기
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, // Overlapped(비동기) 모드
NULL
);
// 2. 이벤트와 OVERLAPPED 구조체 생성
OVERLAPPED aOl[3] = {0};
HANDLE aEvt[3] = {0};
for (int i = 0; i < 3; ++i) {
aEvt[i] = ::CreateEvent(NULL, FALSE, FALSE, NULL);
aOl[i].hEvent = aEvt[i];
}
// 3. 각 Overlapped I/O 요청
aOl[0].Offset = 0; // 파일 처음
aOl[1].Offset = 1024 * 1024 * 128; // 128MB 지점
aOl[2].Offset = 16; // 16바이트 위치
for (int i = 0; i < 3; ++i) {
printf("%d번째 중첩된 쓰기 시도.\n", i);
::WriteFile(hFile, "0123456789", 10, &dwRead, &aOl[i]);
if (::GetLastError() != ERROR_IO_PENDING)
exit(0); // 비동기 요청 실패 시 종료
}
// 4. 3번의 비동기 쓰기가 끝날 때까지 대기
DWORD dwResult = 0;
for (int i = 0; i < 3; ++i) {
dwResult = ::WaitForMultipleObjects(3, aEvt, FALSE, INFINITE);
printf("-> %d번째 쓰기 완료.\n", dwResult - WAIT_OBJECT_0);
}
// (생략) 파일 닫기, 자원 정리
요점 정리
- CreateFile
- FILE_FLAG_OVERLAPPED 플래그로 비동기 쓰기(Overlapped I/O)를 활성화.
- 일반적인 fopen은 비동기를 지원하지 않으므로 사용 불가.
- 이벤트 생성
- I/O가 완료될 때 알림을 받을 이벤트 객체 3개 생성.
- OVERLAPPED 구조체 배열(aOl)의 hEvent 필드에 각각 연결.
- Offset 설정
- aOl[i].Offset = ...로 파일 쓰기 시작 위치 지정.
- 이 위치에 따라 파일이 확 늘어나거나, 특정 지점에 데이터를 기록.
- WriteFile 비동기 호출
- 세 번의 쓰기 요청을 연속으로 진행.
- 즉시 반환되며, 실제 쓰기는 OS 내부 큐에서 스케줄링.
- WaitForMultipleObjects
- 한 번에 3개의 이벤트를 기다림.
- 이벤트가 SET되면(= 쓰기 요청 완료) 반환, 이를 3번 반복해 모든 쓰기 완료를 감시.
3. 주요 개념 상세 설명
3.1 비동기 I/O 요청과 큐
- 비동기 Write:
- WriteFile(..., &aOl[i]) 호출 시, OS는 해당 요청을 큐에 등록.
- 함수는 즉시 리턴(에러 코드는 ERROR_IO_PENDING).
- OS 내부 루프:
- 큐에서 요청을 꺼내 실제 디스크에 쓰기 수행.
- 완료 시점에 aOl[i].hEvent를 SET.
3.2 이벤트와 WaitForMultipleObjects
- 이벤트(Event):
- OS가 I/O 완료 시 SetEvent 호출로 알리는 동기화 객체.
- WaitForMultipleObjects(3, aEvt, FALSE, INFINITE):
- 이벤트 배열 aEvt[3] 중 어느 하나가 SET되면 즉시 복귀( FALSE = WaitAny 모드 ).
- 반환값은 WAIT_OBJECT_0 + 인덱스.
- 이 인덱스를 사용해 어떤 요청이 끝났는지 알 수 있음.
3.3 파일 Offset과 파일 크기 증가
- aOl[0].Offset = 0: 파일 시작 부분에 10바이트 기록.
- aOl[1].Offset = 128MB: 파일이 아직 128MB 크기에 미치지 않았어도, OS는 그 지점을 작성 → 파일이 확장됨.
- aOl[2].Offset = 16: 16바이트 위치에 “0123456789”를 기록 → 앞부분에 빈 공간(‘\0’ 등) 생길 수 있음.
4. 동기 vs 비동기: 왜 비동기가 필요한가?
4.1 동기 방식의 문제
- 요청 후 완료까지 대기(블로킹).
- 여러 개의 큰 I/O 작업을 동시에 하려면 스레드가 여러 개 필요 → 성능 저하, 자원 낭비.
4.2 비동기 방식의 이점
- CPU와 I/O 분리:
- CPU는 다른 작업(연산, UI 등)을 진행, I/O는 OS 내부 스케줄링.
- 고성능:
- I/O가 많은 서버 환경에서 대규모 동시 처리 가능.
5. 결론 및 요약
- CreateFile + FILE_FLAG_OVERLAPPED: 비동기(Overlapped) I/O를 활성화하는 핵심.
- WriteFile(... Overlapped ...): 요청이 큐에 등록되어 즉시 반환, 실제 쓰기는 백그라운드에서 진행.
- 이벤트(Event) 객체: 각 요청 완료 시 OS가 SetEvent로 알림 → WaitForMultipleObjects 등으로 감시.
- Offset 설정: 같은 파일에 여러 위치를 동시에 쓰는 예시. 파일 크기를 확장하는 작업은 OS가 처리.
이처럼 Overlapped I/O(비동기)는 고성능 파일 I/O나 대규모 서버에서 필수적인 기술입니다. 파일 쓰기를 여러 번 동시에 실행해도, OS가 큐에서 스케줄링하여 병렬로 처리하고 완료 이벤트로 알릴 수 있기 때문에, CPU 사용 효율과 쓰레드 최소화라는 측면에서 강력한 이점을 제공합니다.
'프로그래밍 > 소켓 프로그래밍 입문' 카테고리의 다른 글
콜백 기반 비동기 파일 I/O: 내부 동작 상세 (0) | 2025.01.24 |
---|---|
콜백 기반 비동기 파일 I/O: 간략하게 살펴보기 (0) | 2025.01.24 |
윈도우 운영체제에서의 파일 I/O 구조: 요청과 처리 (0) | 2025.01.24 |
실제 파일 송수신 테스트 해보기 (0) | 2025.01.21 |
프로토콜이 적용된 파일 수신 클라이언트 제작 (0) | 2025.01.21 |