비동기 I/O (Asynchronous I/O)
만들어 놓은 프로그램이 CPU 사용량을 확인했을데 들쭉날쭉하다면 뭔가 잘못만들어 졌을 가능성이 매우 큽니다.
사용량이 큰 폭으로 사용된다면 성능저하가 일어나기 때문입니다.
가령 모든 데이터를 받아낸 뒤에 실행이 된다면 데이터를 받아오는 동안에는 어떠한 작업도 하지 못하게 됩니다. 예를 들어 동영상을 볼 때 동영상 데이터를 모두 받아온 뒤 영상이 나온다면 그 동안은 영상을 볼 수가 없게 되는 것이죠. 이러한 방식은 동기 I/O (Synchronous I/O)라 합니다.
ANSI 표준 함수를 이용해 만들어진 프로그램들은 Synchronous I/O 방식으로 만들어 집니다. 한번 호출되면 완료될 때까지 유지되는 현상을 Blocking이라 하는데 이러한 함수들을 가리켜 Blocking 함수라고 하며 이 함수들을 이용한 입출력 연산을 Synchronous I/O라고 하는 것입니다.
우리가 동영상을 보고자 할 때 대부분 실행과 동시에 영상이 나오게 됩니다. 물론 엄청난 사양으로 인해 동기I/O 방식으로 처리되었을 수도 있습니다.
하지만 그만한 사양을 가지고 있는 사람들은 극히 드물죠. 따라서 데이터 수신과, 영상 출력이 동시에 이루어지도록 합니다. 이러한 방식을 비동기I/O라고 합니다.
중첩 I/O (Overlapped I/O)
비동기 I/O 방식 중에 대표적인 것이 중첩 I/O 입니다. 가령 fread 함수를 이용하여 텍스트를 출력해주는 프로그램이 있다고 하면 fread가 끝난 뒤에 출력이 이루어질 것입니다.
앞서 ANSI 표준 함수로 만들어진 프로그램은 Blocking 함수 Synchronous I/O 라고 했습니다. 그렇다면 이러한 방식이 아니라 Non-Blocking 방식의 함수를 사용해야 하는데 어떤 함수가 있을까요?
ReadFile 함수가 Non-Blocking 함수입니다. 이 함수는 실행이 되자마자 바로 반환합니다. 따라서 지속적으로 데이터가 들어와도 바로 반환되기 때문에 I/O 중첩이 일어나도 실행이 됩니다.
이러한 이유로 중첩 I/O라고 부릅니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | #include <stdio.h> #include <tchar.h> #include <Windows.h> #define buf_size 1024 int CommToClient(HANDLE); int _tmain(int argc, TCHAR* argv[]) { LPTSTR pipeName = _T("\\\\.\\pipe\\simple_pipe"); HANDLE hPipe; while (1) { hPipe = CreateNamedPipe(pipeName, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, buf_size / 2, buf_size / 2, 20000, NULL); if (hPipe == NULL) { printf("CreateNamedPipe failed \n"); return -1; } BOOL isSuccess; isSuccess = ConnectNamedPipe(hPipe, NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED); if (isSuccess) { CommToClient(hPipe); } else { CloseHandle(hPipe); } return 1; } return 0; } int CommToClient(HANDLE hPipe) { TCHAR fileName[MAX_PATH]; TCHAR dataBuf[buf_size]; BOOL isSuccess; DWORD fileNameSize; isSuccess = ReadFile(hPipe, fileName, MAX_PATH*sizeof(TCHAR), &fileNameSize, NULL); if (!isSuccess || fileNameSize == 0) { printf("Pipe read message error! \n"); return -1; } FILE* fileptr = _tfopen(fileName, _T("r")); if (fileptr == NULL) { printf("File Open Error \n"); return -1; } OVERLAPPED overlappedInst; memset(&overlappedInst, 0, sizeof(overlappedInst)); overlappedInst.hEvent = CreateEvent(NULL, TRUE, TRUE, NULL); DWORD bytesWritten = 0, bytesRead = 0, bytesWrite = 0, bytesTransfer = 0; while (!feof(fileptr)) { bytesRead = fread(dataBuf, 1, buf_size, fileptr); bytesWrite = bytesRead; isSuccess = WriteFile(hPipe, dataBuf, bytesWrite, &bytesWritten, &overlappedInst); if (!isSuccess && GetLastError() != ERROR_IO_PENDING) { printf("Pipe write message error! \n"); break; } WaitForSingleObject(overlappedInst.hEvent, INFINITE); GetOverlappedResult(hPipe, &overlappedInst, &bytesTransfer, FALSE); printf("Treansferred data size: %u \n", bytesTransfer); } FlushFileBuffers(hPipe); DisconnectNamedPipe(hPipe); CloseHandle(hPipe); return 1; } | cs |
16라인 CreateNamedPipe에서 FILE_FALG_OVERLAPPED 인자를 넣고 있는데 이렇게 생성된 파이프는 Non-Blocking 모드로 동작한다.
67라인에서는 hEvent의 값을 CreateEvent()로 넣어주고 있다. 초기 상태가 Signaled 상태로 되어있는데 입출력 완료를 확인하기 위한 용도로 반복적으로 사용되므로 71라인 while문에서 Non-Signaled 상태로 변경시켜야 합니다. 하지만 코드상 Non-Signaled 상태로 바꾸는 코드는 없는데, ReadFile 함수의 특성에 따라 자동으로 Non-Signaled 상태가 됩니다.
중첩 I/O에서는 WriteFile, ReadFile 함수가 NULL을 반환했다고 오류로 취급을 하면 안된다. 하나의 I/O연산이 끝나기 전에 새로운 I/O 작업이 들어올 수 있기 때문입니다.
따라서 NULL을 반환했다고 한다면 GetLastError()을 이용해서 I/O 요청에 의한 것인지 아닌지를 확인해야 합니다.
완료루틴 I/O (Completion Routin I/O)
중첩 I/O를 보면 입력 및 출력이 완료되었음을 확인하는 번거로운 작업을 항상 고려해야 했습니다.
가령 A, B, C I/O 작업이 완료되었을 때 D, E, F 루틴이 실행된다고 했을 때를 완료루틴이라고 합니다.
A I/O 가 끝나고 D라는 루틴이 실행되어야 한다면 구현을 해야 하는데, 중첩으로 이루어진 A, B, C 중 어느것이 완료가 되었는지 구분해야합니다.
위와 같이 루틴을 정해야 할 때 WritefileEx, ReadFileEx 함수를 사용합니다. 이 두 함수는 비동기 중에서 확장 I/O를 위한 함수입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | #include <stdio.h> #include <tchar.h> #include <Windows.h> TCHAR strdata[] = _T("Nobody was father off base than the pundits who said \n Royal Liverppol was outdated and not worthy of hosting ~ \n for the first time since 1967. The Hoylake track ~ \n Here's the solution to modern golf technology -- firm, \n fast fairways, penal bunkers, firm greens and, with any ~ \n"); VOID WINAPI FileIoCompletionRoutine(DWORD, DWORD, LPOVERLAPPED); int _tmain(int argc, TCHAR* argv[]) { TCHAR fileName[] = _T("data.txt"); HANDLE hFile = CreateFile(fileName, GENERIC_WRITE, FILE_SHARE_WRITE, 0, CREATE_ALWAYS, FILE_FLAG_OVERLAPPED, 0); if (hFile == INVALID_HANDLE_VALUE) { printf("File creation fault! \n"); return -1; } OVERLAPPED overlappedInst; memset(&overlappedInst, 0, sizeof(overlappedInst)); overlappedInst.hEvent = (HANDLE)1234; WriteFileEx(hFile, strdata, sizeof(strdata), &overlappedInst, FileIoCompletionRoutine); SleepEx(INFINITE, TRUE); CloseHandle(hFile); return 0; } VOID WINAPI FileIoCompletionRoutine(DWORD errorCode, DWORD numOfBytesTransfered, LPOVERLAPPED overlapped) { printf("File write result \n"); printf("Error code : %u \n", errorCode); printf("Transfered bytes len: %u \n", numOfBytesTransfered); printf("The other info: %u \n", (DWORD)overlapped->hEvent); } | cs |
위 예제를 실행시키면 잘 실행이된다. 25라인의 SleepEx를 주석처리하면 출력이 정상적으로 안되는 것을 볼 수 있다.
하지만 파일은 정상적으로 만들어진 것을 볼 수 있다. 두 차이는 메인 쓰레드가 알림 가능한 상태가 아니기 때문이다.
알림 가능한 상태 (Alertable State)
I/O 연산이 완료되어 완료루틴을 실행할 차례가 되었다면 바로 완료루틴이 실행되어야 하는데 Windows는 완료 루틴 실행 타이밍을 프로그래머가 결정할 수 있도록 한다.
SleepEx()가 완료루틴을 실행하라는 신호를 주는 용도로 사용된다.
A,B,C I/O 연산들이 실행이되고 D라는 연산이 추가적으로 실행될려는 찰라 A,B,C가 모두 완료되어서 완료루틴이 실행된다면 D는 ABC가 모두 끝날 때까지 기다려야만 한다.
완료루틴이 실행되는 타이밍을 프로그래머가 정할 수 있기 때문에 알림 가능한 상태에 바지지 않게만 하면 얼마든지 중첩시킬 수 있다.
WaitForSingleObjectEx, WaitForMultipleObjectsEx 가 쓰레드를 알림 가능한 상태로 만드는 기능을 가지고 있다.
'High Level Technique > Window System' 카테고리의 다른 글
메모리 관리 (0) | 2017.01.10 |
---|---|
SEH (Structured Exception Handling) (0) | 2017.01.09 |
캐쉬와 가상메모리 (0) | 2017.01.05 |
쓰레드 풀 (Thread Pool) (0) | 2017.01.04 |
생산자/소비자 모델 (1) | 2017.01.04 |