동기 및 비동기 I/O

I/O 관련 샘플 애플리케이션도 참조하세요.

입력/출력(I/O) 동기화에는 동기 I/O와 비동기 I/O, 두 가지 유형이 있습니다. 비동기 I/O를 중첩된 I/O라고도 합니다.

동기 파일 I/O에서 스레드는 I/O 작업을 시작하고 I/O 요청이 완료될 때까지 즉시 대기 상태로 돌입합니다. 비동기 파일 I/O를 수행하는 스레드는 적절한 함수를 호출하여 커널에 I/O 요청을 보냅니다. 커널에서 요청을 수락하는 경우, 커널이 스레드에 I/O 작업이 완료되었다는 신호를 보낼 때까지 호출 스레드가 다른 작업을 계속 처리합니다. 그런 다음 현재 작업을 중단하고 필요에 따라 I/O 작업에서 데이터를 처리합니다.

다음 그림에는 두 가지 동기화 유형이 나와 있습니다.

동기 및 비동기 I/O

I/O 요청이 큰 데이터베이스의 새로 고침 또는 백업 또는 느린 통신 링크와 같이 많은 시간이 소요될 것으로 예상되는 경우 비동기 I/O는 일반적으로 처리 효율성을 최적화하는 좋은 방법입니다. 그러나 비교적 빠른 I/O 작업의 경우 커널 I/O 요청 및 커널 신호를 처리하는 오버헤드는 특히 많은 빠른 I/O 작업을 수행해야 하는 경우 비동기 I/O를 덜 유익하게 만들 수 있습니다. 이 경우 동기 I/O가 더 좋습니다. 이러한 작업을 수행하는 방법에 대한 메커니즘 및 구현 세부 정보는 사용되는 디바이스 핸들의 유형 및 애플리케이션의 특정 요구 사항에 따라 달라집니다. 즉, 일반적으로 문제를 해결하는 여러 가지 방법이 있습니다.

동기 및 비동기 I/O 고려 사항

동기 I/O(즉, FILE_FLAG_OVERLAPPED가 지정되지 않음)에 대해 파일 또는 디바이스가 열려 있는 경우 WriteFile 같은 함수에 대한 후속 호출은 다음 이벤트 중 하나가 발생할 때까지 호출 스레드의 실행을 차단할 수 있습니다.

  • I/O 작업이 완료됩니다(이 예제에서는 데이터 쓰기).
  • I/O 오류가 발생했습니다. (예를 들어 파이프는 다른 쪽 끝에서 닫힙니다.)
  • 호출 자체에서 오류가 발생했습니다(예: 하나 이상의 매개 변수가 잘못됨).
  • 프로세스의 다른 스레드는 차단된 스레드의 스레드 핸들을 사용하여 CancelSynchronousIo 함수를 호출합니다. 이 핸들은 해당 스레드에 대한 I/O를 종료하고 I/O 작업은 실패합니다.
  • 차단된 스레드는 시스템에 의해 종료됩니다. 예를 들어 프로세스 자체가 종료되거나 다른 스레드가 차단된 스레드의 핸들을 사용하여 TerminateThread 함수를 호출합니다. (일반적으로 최후의 수단으로 간주되며 애플리케이션 디자인이 좋지 않습니다.)

경우에 따라 이 지연은 애플리케이션의 디자인 및 용도에 허용되지 않을 수 있으므로 애플리케이션 디자이너는 I/O 완료 포트와 같은 적절한 스레드 동기화 개체와 함께 비동기 I/O를 사용하는 것이 좋습니다. 스레드 동기화에 대한 자세한 내용은 동기화 정보를 참조하세요.

프로세스는 dwFlagsAndAttributes 매개 변수에 FILE_FLAG_OVERLAPPED 플래그를 지정하여 CreateFile 호출에서 비동기 I/O에 대한 파일을 엽니다. FILE_FLAG_OVERLAPPED가 지정되지 않으면 파일이 동기 I/O용으로 열립니다. 비동기 I/O에 대해 파일이 열리면 OVERLAPPED 구조체에 대한 포인터가 ReadFileWriteFile 호출에 전달됩니다. 동기 I/O를 수행하는 경우 ReadFileWriteFile 호출에서는 이 구조가 필요하지 않습니다.

참고

비동기 I/O를 위해 파일 또는 디바이스가 열린 경우 해당 핸들을 사용하는 WriteFile 같은 함수에 대한 후속 호출은 일반적으로 즉시 반환되지만 차단된 실행과 관련하여 동기적으로 동작할 수도 있습니다. 자세한 내용은 https://support.microsoft.com/kb/156932를 참조하세요.

 

CreateFile은 파일, 디스크 볼륨, 익명 파이프 및 기타 유사한 디바이스를 여는 데 사용하는 가장 일반적인 기능이지만, I/O 작업은 socket 또는 accept 함수에 의해 생성된 소켓 등의 다른 시스템 객체의 핸들 형식 캐스팅을 사용하여 수행할 수도 있습니다.

디렉터리 개체에 대한 핸들은 FILE_FLAG_BACKUP_SEMANTICS특성의 CreateFile 함수를 호출하여 가져옵니다. 디렉터리 핸들은 거의 사용되지 않습니다. 백업 애플리케이션은 일반적으로 이것을 사용할 몇 안 되는 애플리케이션 중 하나입니다.

비동기 I/O에 대한 파일 개체를 연 후에는 OVERLAPPED 구조체를 올바르게 만들고 초기화하고 ReadFileWriteFile 같은 함수에 대한 각 호출에 전달해야 합니다. 비동기 읽기 및 쓰기 작업에서 OVERLAPPED 구조를 사용할 때 다음 사항에 유의하세요.

  • 파일 개체에 대한 모든 비동기 I/O 작업이 완료될 때까지 OVERLAPPED 구조체 또는 데이터 버퍼의 할당을 취소하거나 수정하지 마세요.
  • OVERLAPPED 구조체에 대한 포인터를 지역 변수로 선언하는 경우 파일 개체에 대한 모든 비동기 I/O 작업이 완료될 때까지 로컬 함수를 종료하지 마세요. 로컬 함수가 조기에 종료되면 OVERLAPPED 구조체가 범위를 벗어나 해당 함수 외부에서 발생하는 ReadFile 또는 WriteFile 함수에 액세스할 수 없습니다.

이벤트를 만들고 OVERLAPPED 구조체에 핸들을 배치할 수도 있습니다. 그런 다음 대기 함수를 사용하여 이벤트 핸들을 대기하여 I/O 작업이 완료될 때까지 기다릴 수 있습니다.

앞에서 설명한 것처럼 비동기 핸들로 작업할 때 애플리케이션은 해당 핸들에서 지정된 I/O 작업과 연결된 리소스를 해제할 시기를 결정할 때 주의해야 합니다. 핸들의 할당이 조기에 취소된 경우 ReadFile 또는 WriteFile이 I/O 작업이 완료되었음을 잘못 보고할 수 있습니다. 또한 WriteFile 함수는 때로 비동기 핸들(ERROR_IO_PENDINGFALSE도 반환할 수 있음)을 사용하더라도 GetLastError 값이 ERROR_SUCCESSTRUE를 반환합니다. 동기 I/O 디자인에 익숙한 프로그래머는 일반적으로 TRUEERROR_SUCCESS가 작업이 완료되었음을 나타내기 때문에 이 시점에서 데이터 버퍼 리소스를 해제합니다. 그러나 이 비동기 핸들과 함께 I/O 완료 포트를 사용하는 경우 I/O 작업이 즉시 완료되더라도 완료 패킷도 전송됩니다. 즉, WriteFile이 I/O 완료 포트 루틴 외에 ERROR_SUCCESSTRUE를 반환한 후 애플리케이션이 리소스를 해제하면 double-free 오류 조건이 발생합니다. 이 예제에서는 완료 포트 루틴이 이러한 리소스에 대한 모든 해제 작업을 전적으로 담당하도록 허용하는 것이 좋습니다.

시스템은 파일 포인터(즉, 검색 디바이스)를 지원하는 파일 및 디바이스에 대한 비동기 핸들에 대한 파일 포인터를 유지 관리하지 않으므로 파일 위치는 OVERLAPPED 구조의 관련 오프셋 데이터 멤버에 있는 읽기 및 쓰기 함수에 전달되어야 합니다. 자세한 내용은 WriteFileReadFile을 참조하세요.

동기 핸들에 대한 파일 포인터 위치는 데이터가 읽히거나 쓰일 때 시스템에서 유지 관리되며 SetFilePointer 또는 SetFilePointerEx 함수를 사용하여 업데이트할 수도 있습니다.

애플리케이션은 파일 핸들을 대기하여 I/O 작업의 완료를 동기화할 수도 있지만 이렇게 하려면 각별히 주의해야 합니다. I/O 작업이 시작될 때마다 운영 체제는 파일 핸들을 신호 없는 상태로 설정합니다. I/O 작업이 시료될 때마다 운영 체제는 파일 핸들을 신호 있는 상태로 설정합니다. 따라서 애플리케이션이 두 가지 I/O 작업을 시작하고 파일 핸들에서 대기하는 경우 핸들이 신호 있는 상태로 설정될 때 어떤 작업이 완료되는지 확인할 방법은 없습니다. 애플리케이션이 단일 파일에서 여러 비동기 I/O 작업을 수행해야 하는 경우 공통 파일 핸들이 아닌 각 I/O 작업에 대해 특정 OVERLAPPED 구조의 이벤트 핸들을 기다려야 합니다.

보류 중인 모든 비동기 I/O 작업을 취소하려면 다음 중 하나를 사용합니다.

  • CancelIo - 이 함수는 지정된 파일 핸들에 대해 호출 스레드에서 실행한 작업만 취소합니다.
  • CancelIoEx - 이 함수는 지정된 파일 핸들에 대해 스레드에서 실행한 모든 작업을 취소합니다.

보류 중인 동기 I/O 작업을 취소하려면 CancelSynchronousIo를 사용하세요.

ReadFileExWriteFileEx 함수를 사용하면 비동기 I/O 요청이 완료될 때 실행할 루틴을 애플리케이션이 지정할 수 있습니다(FileIOCompletionRoutine 참조).