Windows에서 비동기 디스크 I/O가 동기로 표시됨
이 문서는 I/O의 기본 동작이 동기적이지만 비동기적으로 표시되는 문제를 해결하는 데 도움이 됩니다.
원래 제품 버전: Windows
원래 KB 번호: 156932
요약
Microsoft Windows의 파일 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 함수(예: CreateFile
, ReadFile
WriteFile
.
비동기 I/O 작업은 종종 동기 I/O처럼 동작합니다. 이 문서에서 설명하는 특정 조건은 이후 섹션에서 설명하므로 I/O 작업이 동기적으로 완료됩니다. I/O 함수는 I/O가 완료될 때까지 반환되지 않으므로 호출자는 백그라운드 작업을 수행할 시간이 없습니다.
여러 함수는 동기 및 비동기 I/O와 관련이 있습니다. 이 문서에서는 예제로 사용합니다 ReadFile
WriteFile
. 좋은 대안은 될 ReadFileEx
것입니다 .WriteFileEx
이 문서에서는 특히 디스크 I/O만 설명하지만 직렬 I/O 또는 네트워크 I/O와 같은 다른 유형의 I/O에 많은 원칙을 적용할 수 있습니다.
비동기 I/O 설정
FILE_FLAG_OVERLAPPED
파일이 열릴 때 플래그를 지정 CreateFile
해야 합니다. 이 플래그를 사용하면 파일에 대한 I/O 작업을 비동기적으로 수행할 수 있습니다. 예를 들어 다음과 같습니다.
HANDLE hFile;
hFile = CreateFile(szFileName,
GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
FILE_FLAG_NORMAL | FILE_FLAG_OVERLAPPED,
NULL);
if (hFile == INVALID_HANDLE_VALUE)
ErrorOpeningFile();
비동기 I/O를 코딩할 때는 필요한 경우 시스템이 작업을 동기식으로 만들 수 있는 권한이 있으므로 주의해야 합니다. 따라서 동기적으로 또는 비동기적으로 완료될 수 있는 I/O 작업을 올바르게 처리하는 프로그램을 작성하는 것이 가장 좋습니다. 샘플 코드는 이 고려 사항을 보여 줍니다.
비동기 작업이 완료되기를 기다리는 동안 프로그램에서 수행할 수 있는 많은 작업(예: 추가 작업 큐 대기 또는 백그라운드 작업 수행)이 있습니다. 예를 들어 다음 코드는 읽기 작업의 겹치고 겹치지 않는 완료를 올바르게 처리합니다. 처리 중인 I/O가 완료되기를 기다리는 것 외에는 아무 작업도 수행하지 않습니다.
if (!ReadFile(hFile,
pDataBuf,
dwSizeOfBuffer,
&NumberOfBytesRead,
&osReadOperation )
{
if (GetLastError() != ERROR_IO_PENDING)
{
// Some other error occurred while reading the file.
ErrorReadingFile();
ExitProcess(0);
}
else
// Operation has been queued and
// will complete in the future.
fOverlapped = TRUE;
}
else
// Operation has completed immediately.
fOverlapped = FALSE;
if (fOverlapped)
{
// Wait for the operation to complete before continuing.
// You could do some background work if you wanted to.
if (GetOverlappedResult( hFile,
&osReadOperation,
&NumberOfBytesTransferred,
TRUE))
ReadHasCompleted(NumberOfBytesTransferred);
else
// Operation has completed, but it failed.
ErrorReadingFile();
}
else
ReadHasCompleted(NumberOfBytesRead);
참고 항목
&NumberOfBytesRead
전달되는 ReadFile
형식은 전달된 값과 GetOverlappedResult
다릅니다&NumberOfBytesTransferred
. 작업이 비동 GetOverlappedResult
기식으로 수행된 경우 작업이 완료된 후 작업에서 전송된 실제 바이트 수를 확인하는 데 사용됩니다. 전달된 ReadFile
내용은 &NumberOfBytesRead
의미가 없습니다.
반면에 작업이 즉시 &NumberOfBytesRead
완료된 경우 전달된 ReadFile
작업은 읽은 바이트 수에 유효합니다. 이 경우 전달된 ReadFile
구조를 무시 OVERLAPPED
합니다. 사용하거나 WaitForSingleObject
사용하지 GetOverlappedResult
마세요.
비동기 작업의 또 다른 주의 사항은 보류 중인 작업이 완료될 때까지 구조를 사용하지 OVERLAPPED
않아야 한다는 것입니다. 즉, 3개의 미해결 I/O 작업이 있는 경우 세 OVERLAPPED
가지 구조를 사용해야 합니다. 구조를 다시 사용하는 OVERLAPPED
경우 I/O 작업에서 예측할 수 없는 결과를 받게 되며 데이터 손상이 발생할 수 있습니다. 또한 구조체를 처음 사용하거나 이전 작업이 완료된 후 다시 사용하기 OVERLAPPED
전에 왼쪽 오버 데이터가 새 작업에 영향을 주지 않도록 올바르게 초기화해야 합니다.
작업에 사용되는 데이터 버퍼에 동일한 유형의 제한이 적용됩니다. 데이터 버퍼는 해당 I/O 작업이 완료될 때까지 읽거나 쓸 수 없습니다. 버퍼를 읽거나 쓰면 오류가 발생하고 데이터가 손상될 수 있습니다.
비동기 I/O는 여전히 동기로 표시됩니다.
그러나 이 문서의 앞부분에 있는 지침을 따른 경우 모든 I/O 작업은 일반적으로 발급된 순서대로 동기적으로 완료되며 반환 시 FALSE GetLastError()
ERROR_IO_PENDING
를 반환하지 ReadFile
않습니다. 즉, 백그라운드 작업에 대한 시간이 없습니다. 왜 이런 일이 발생합니까?
비동기 작업에 대해 코딩한 경우에도 I/O 작업이 동기적으로 완료되는 데는 여러 가지 이유가 있습니다.
압축
비동기 작업에 방해가 되는 한 가지는 NTFS(New Technology File System) 압축입니다. 파일 시스템 드라이버는 압축된 파일에 비동기적으로 액세스하지 않습니다. 대신 모든 작업이 동기식으로 만들어집니다. 이 장애는 COMPRESS 또는 PKZIP와 유사한 유틸리티로 압축된 파일에는 적용되지 않습니다.
NTFS 암호화
압축과 마찬가지로 파일 암호화를 사용하면 시스템 드라이버가 비동기 I/O를 동기로 변환합니다. 파일의 암호가 해독되면 I/O 요청은 비동기적입니다.
파일 확장
I/O 작업이 동기적으로 완료되는 또 다른 이유는 작업 자체입니다. Windows에서 길이를 확장하는 파일에 대한 쓰기 작업은 동기식입니다.
참고 항목
애플리케이션은 함수를 사용하여 SetFileValidData
파일의 유효한 데이터 길이를 변경한 다음 실행하여 앞에서 언급한 쓰기 작업을 비동기식으로 만들 수 있습니다 WriteFile
.
애플리케이션은 Windows XP 이상 버전에서 사용할 수 있는 파일을 사용하여 SetFileValidData
0 채우기에 대한 성능 저하를 초래하지 않고 파일을 효율적으로 확장할 수 있습니다.
NTFS 파일 시스템에서 정의된 유효한 VDL(데이터 길이) SetFileValidData
까지 데이터를 0으로 채우지 않으므로 이 함수는 이전에 다른 파일에서 차지했던 클러스터를 파일에 할당할 수 있는 보안에 영향을 줍니다. 따라서 SetFileValidData
호출자에게 새 SeManageVolumePrivilege
사용이 설정되어 있어야 합니다(기본적으로 관리자에게만 할당됨). MICROSOFT는 ISV(독립 소프트웨어 공급업체)가 이러한 기능을 사용할 때의 의미를 신중하게 고려하는 것이 좋습니다.
캐시
대부분의 I/O 드라이버(디스크, 통신 등)에는 I/O 요청을 즉시 완료할 수 있는 경우 작업이 완료되고 ReadFile
또는 WriteFile
함수가 TRUE를 반환하는 특수 사례 코드가 있습니다. 모든 면에서 이러한 유형의 작업은 동기식으로 보입니다. 디스크 디바이스의 경우 일반적으로 데이터가 메모리에 캐시될 때 I/O 요청을 즉시 완료할 수 있습니다.
데이터가 캐시에 없습니다.
그러나 데이터가 캐시에 없는 경우 캐시 구성표가 작동할 수 있습니다. Windows 캐시는 파일 매핑을 사용하여 내부적으로 구현됩니다. Windows의 메모리 관리자는 캐시 관리자에서 사용하는 파일 매핑을 관리하는 비동기 페이지 오류 메커니즘을 제공하지 않습니다. 캐시 관리자는 요청된 페이지가 메모리에 있는지 확인할 수 있으므로 비동기 캐시된 읽기를 실행하고 페이지가 메모리에 없는 경우 파일 시스템 드라이버는 스레드를 차단하지 않으려는 것으로 가정하고 제한된 작업자 스레드 풀에서 요청을 처리합니다. 읽기가 아직 보류 중인 상태에서 호출한 후 ReadFile
컨트롤이 프로그램에 반환됩니다.
이는 적은 수의 요청에서 잘 작동하지만 작업자 스레드 풀이 제한되어 있기 때문에(현재 16MB 시스템에서 3개), 특정 시간에 디스크 드라이버에 큐에 대기 중인 요청은 몇 개뿐입니다. 캐시에 없는 데이터에 대해 수많은 I/O 작업을 실행하면 캐시 관리자와 메모리 관리자가 포화 상태가 되고 요청이 동기식으로 만들어집니다.
캐시 관리자의 동작은 순차적으로 또는 임의로 파일에 액세스하는지 여부에 따라 영향을 받을 수도 있습니다. 캐시의 이점은 순차적으로 파일에 액세스할 때 가장 많이 표시됩니다. 호출의 CreateFile
플래그는 FILE_FLAG_SEQUENTIAL_SCAN
이러한 유형의 액세스에 대한 캐시를 최적화합니다. 그러나 임의 방식으로 파일에 액세스하는 경우 플래그 CreateFile
를 FILE_FLAG_RANDOM_ACCESS
사용하여 캐시 관리자에게 임의 액세스에 대한 동작을 최적화하도록 지시합니다.
캐시 사용 안 함
플래그는 FILE_FLAG_NO_BUFFERING
비동기 작업에 대한 파일 시스템의 동작에 가장 큰 영향을 미칩니다. I/O 요청이 비동기임을 보장하는 가장 좋은 방법입니다. 파일 시스템에 캐시 메커니즘을 전혀 사용하지 않도록 지시합니다.
참고 항목
데이터 버퍼 맞춤 및 디바이스의 섹터 크기와 관련이 있는 이 플래그를 사용하는 데는 몇 가지 제한 사항이 있습니다. 자세한 내용은 이 플래그를 올바르게 사용하는 방법에 대한 CreateFile 함수 설명서의 함수 참조를 참조하세요.
실제 테스트 결과
다음은 샘플 코드의 몇 가지 테스트 결과입니다. 숫자의 크기는 여기서 중요하지 않으며 컴퓨터마다 다르지만 서로 비교된 숫자의 관계는 성능에 대한 플래그의 일반적인 영향을 조명합니다.
다음 중 하나와 유사한 결과가 표시되도록 할 수 있습니다.
테스트 1
Asynchronous, unbuffered I/O: asynchio /f*.dat /n Operations completed out of the order in which they were requested. 500 requests queued in 0.224264 second. 500 requests completed in 4.982481 seconds.
이 테스트는 앞에서 언급한 프로그램이 500개의 I/O 요청을 신속하게 실행했으며 다른 작업을 수행하거나 더 많은 요청을 발급하는 데 많은 시간을 할애했음을 보여줍니다.
테스트 2
Synchronous, unbuffered I/O: asynchio /f*.dat /s /n Operations completed in the order issued. 500 requests queued and completed in 4.495806 seconds.
이 테스트는 이 프로그램이 ReadFile을 호출하여 작업을 완료하는 데 4.495880초가 소요되었지만 테스트 1에서 동일한 요청을 발급하는 데 0.224264초밖에 소비하지 않은 것을 보여 줍니다. 테스트 2에서는 프로그램이 백그라운드 작업을 수행할 수 있는 추가 시간이 없었습니다.
테스트 3
Asynchronous, buffered I/O: asynchio /f*.dat Operations completed in the order issued. 500 requests issued and completed in 0.251670 second.
이 테스트는 캐시의 동기 특성을 보여 줍니다. 모든 읽기는 0.251670초에 실행되고 완료되었습니다. 즉, 비동기 요청이 동기적으로 완료되었습니다. 또한 이 테스트는 데이터가 캐시에 있을 때 캐시 관리자의 고성능을 보여 줍니다.
테스트 4
Synchronous, buffered I/O: asynchio /f*.dat /s Operations completed in the order issued. 500 requests and completed in 0.217011 seconds.
이 테스트는 테스트 3과 동일한 결과를 보여 줍니다. 캐시의 동기 읽기는 캐시에서 비동기 읽기보다 조금 더 빠르게 완료됩니다. 또한 이 테스트는 데이터가 캐시에 있을 때 캐시 관리자의 고성능을 보여 줍니다.
결론
프로그램이 수행하는 작업의 유형, 크기 및 수에 따라 모두 달라지므로 가장 적합한 메서드를 결정할 수 있습니다.
특별한 플래그 CreateFile
를 지정하지 않고 기본 파일 액세스는 동기 및 캐시된 작업입니다.
참고 항목
파일 시스템 드라이버가 수정된 데이터의 예측 비동기 읽기 및 비동기 지연 쓰기를 수행하므로 이 모드에서 일부 자동 비동기 동작이 수행됩니다. 이 동작은 애플리케이션의 I/O를 비동기화하지는 않지만 대부분의 간단한 애플리케이션에 이상적인 사례입니다.
반면, 애플리케이션이 간단하지 않은 경우 이 문서의 앞부분에서 설명한 테스트와 유사하게 최상의 방법을 결정하기 위해 몇 가지 프로파일링 및 성능 모니터링을 수행해야 할 수 있습니다. 또는 WriteFile
함수에서 ReadFile
소요된 시간을 프로파일링한 다음 이 시간을 실제 I/O 작업이 완료되는 데 걸리는 시간과 비교하는 것이 유용합니다. I/O를 실제로 발급하는 데 대부분의 시간이 소요되는 경우 I/O가 동기적으로 완료됩니다. 그러나 I/O 요청을 실행하는 데 소요된 시간이 I/O 작업이 완료되는 데 걸리는 시간에 비해 상대적으로 작으면 작업이 비동기적으로 처리됩니다. 이 문서의 앞부분에서 언급한 샘플 코드는 함수를 QueryPerformanceCounter
사용하여 자체 내부 프로파일링을 수행합니다.
성능 모니터링은 프로그램이 디스크와 캐시를 얼마나 효율적으로 사용하는지 결정하는 데 도움이 될 수 있습니다. Cache 개체에 대한 성능 카운터를 추적하면 캐시 관리자의 성능이 표시됩니다. 실제 디스크 또는 논리 디스크 개체의 성능 카운터를 추적하면 디스크 시스템의 성능이 표시됩니다.
성능 모니터링에 유용한 몇 가지 유틸리티가 있습니다. PerfMon
특히 DiskPerf
유용합니다. 시스템에서 디스크 시스템의 성능에 대한 데이터를 수집하려면 먼저 명령을 실행 DiskPerf
해야 합니다. 명령을 실행한 후 시스템을 다시 시작하여 데이터 수집을 시작해야 합니다.