(Win32 : WindowsProgramming-29) Async IO

Posted by : at

Category : win32   WindowsProgramming


#include <Windows.h>
#include <stdio.h>
#include <conio.h>

int main()
{
    // 계산기 파일을 오픈
    HANDLE hFile = CreateFileA("C:\\Windows\\system32\\calc.exe",
                                GENERIC_READ, FILE_SHARE_READ,
                                0, OPEN_EXISTING,
                                FILE_ATTRIBUTE_NORMAL, 0);

    if(hFile == INVALID_HANDLE_VALUE)
    {
        printf("Error in Open File\n");
    }

    DWORD len = 0;
    char buff[4096] = {0};
    // 버퍼에 계산기 파일을 읽은 데이터를 쓴다
    BOOL ret = ReadFile(hFile, buff, 4096, &len, 0);

    // ReadFile를 통해 버퍼에 쓰는 시간이 걸릴텐데 버퍼에 다 쓰여진 후 다음 라인으로 넘어가게 된다.
    // 이런 방식을 동기(Synchronous)화 방식 이라한다.

    if(ret == FALSE)
    {
        printf("Read Error\n");
    }
    else
    {
        printf("Comleting writes synchronosly\n");
    }

    CloseHandle(hFile);
    _getch();
}

여기서 단점은 I/O작업(ReadFile/WriteFile)은 CPU와는 상관없이 I/O 제어칩에 의해서 처리되는데 CPU자원의 낭비라할 수 있다.

비동기 방식으로 처리하면 성능의 향상을 가져올 수 있다.

#include <Windows.h>
#include <stdio.h>
#include <conio.h>

int main()
{
    HANDLE hFile = CreateFileA("C:\\Windows\\system32\\calc.exe",
                                GENERIC_READ, FILE_SHARE_READ,
                                0, OPEN_EXISTING,
                                FILE_FLAG_OVERLAPPED    // 비동기를 위한 옵션
                                , 0);

    if(hFile == INVALID_HANDLE_VALUE)
    {
        printf("Error in Open File\n");
    }

    char buff[4096] = {0};

    // 비동기를 위해서 OVERLAPPED 구조체 생성
    OVERLAPPED ov = { 0 };

    // 비동기로 할시 ret은 무조건 FALSE
    BOOL ret = ReadFile(hFile, buff, 4096, 0, &ov);


    if(ret == FALSE)
    {
        if(GetLastError() == ERROR_IO_PENNDING)
            printf("Read Error\n");
    }
    else
    {
        printf("Comleting writes synchronosly\n");
    }

    CloseHandle(hFile);
    _getch();
}

ReadFile이 끝난경우의 처리?

네 가지 방법이 존재한다.

  • Device Signal 대기
  • Event Kernel Object Signal 대기
  • IO Callback 함수 사용
  • IOCP (다음강에 별도로 설명)

Device Kernel Object Signal 대기 방법

File Kernel Object도 결국은 Kernel Object이기에 signal을 갖는다

File Kernel Object는 I/O 작업이 시작하기 전에 non signal
I/O 작업이 완료되면 Signal 상태로 변경된다.

이 방식의 단점은 하나의 파일에 여러개의 비동기 I/O를 요청한 경우 구별이 불가능하다

// 이렇게 요청하면 buff1, buff2중 어떤게 Read를 했는지 분간이 안된다는 말
char buff1[4096] = {0};
char buff2[4096] = {0};
OVERLAPPED ov = { 0 };
OVERLAPPED ov2 = { 0 };

BOOL ret = ReadFile(hFile, buff1, 4096, 0, &ov);
BOOL ret2 = ReadFile(hFile, buff2, 4096, 0, &ov2);
#include <Windows.h>
#include <stdio.h>
#include <conio.h>

int main()
{
    HANDLE hFile = CreateFileA("C:\\Windows\\system32\\calc.exe",
                                GENERIC_READ, FILE_SHARE_READ,
                                0, OPEN_EXISTING,
                                FILE_FLAG_OVERLAPPED, 
                                0);

    if(hFile == INVALID_HANDLE_VALUE)
    {
        printf("Error in Open File\n");
    }

    char buff[4096] = {0};
    OVERLAPPED ov = { 0 };

    BOOL ret = ReadFile(hFile, buff, 4096, 0, &ov);


    if(ret == FALSE)
    {
        if(GetLastError() == ERROR_IO_PENNDING)
            printf("Read Error\n");

        // File Kernel Object가 Signal될때까지 대기
        WiatForSingleObject(hFile, INFINITE);
        printf("complet io\n");
    }
    else
    {
        printf("Comleting writes synchronosly\n");
    }

    CloseHandle(hFile);
    _getch();
}

Event Kernel Object Signal 대기

OVERLAPPED 구조체에 event 등록
여러개의 File I/O를 처리가능

#include <Windows.h>
#include <stdio.h>
#include <conio.h>

int main()
{
    HANDLE hFile = CreateFileA("C:\\Windows\\system32\\calc.exe",
                                GENERIC_READ, FILE_SHARE_READ,
                                0, OPEN_EXISTING,
                                FILE_FLAG_OVERLAPPED, 
                                0);

    if(hFile == INVALID_HANDLE_VALUE)
    {
        printf("Error in Open File\n");
    }

    char buff[4096] = {0};
    OVERLAPPED ov = { 0 };
    // Event 등록
    ov.hEvent = CreateEvent(0, 0, 0, 0);

    BOOL ret = ReadFile(hFile, buff, 4096, 0, &ov);

    OVERLAPPED ov2 = { 0 };
    ov2.Offset = 4096;              // Offset도 넣을 수 있다.
    // 파일에 4k떨어진 지점부터 읽기시작해라
    ov2.hEvent = CreateEvent(0, 0, 0, 0);

    BOOL ret2 = ReadFile(hFile, buff, 4096, 0, &ov2);


    if(ret == FALSE)
    {
        if(GetLastError() == ERROR_IO_PENNDING)
            printf("Read Error\n");

        // Event를 대기
        WaitForSingleObject(ov.hEvent, INFINITE);
    }
    else
    {
        printf("Comleting writes synchronosly\n");
    }

    CloseHandle(hFile);
    _getch();
}

IO Callback 함수 사용

이 방식도 잘 사용하지 않는게, 비동기를 요청한 스레드가 CallBack함수를 실행하게 된다.
다른 방식의 경우 비동기를 요청한 스레드가 아닌 다른 스레드가 대기할 수 있다.

#include <Windows.h>
#include <stdio.h>
#include <conio.h>

void __stdcall foo(DWORD error, DWORD bytes, LPOVERLAPPED ov)
{
    printf("foo\n");
    printf("error : %d\n", error);
    printf("bytes : %d\n", bytes);
}

int main()
{
    HANDLE hFile = CreateFileA("C:\\Windows\\system32\\calc.exe",
                                GENERIC_READ, FILE_SHARE_READ,
                                0, OPEN_EXISTING,
                                FILE_FLAG_OVERLAPPED, 
                                0);

    if(hFile == INVALID_HANDLE_VALUE)
    {
        printf("Error in Open File\n");
    }

    char buff[4096] = {0};
    OVERLAPPED ov = { 0 };

    // ReadFile 대신에 ReadFileEx를 사용함(비동기 전용)
    // BOOL ret = ReadFile(hFile, buff, 4096, 0, &ov);
    BOOL ret = ReadFileEx(hFile, buff, 4096, &ov, foo); // 종료 후 foo를 호출해달라

    if(ret == TRUE)
    {
        printf("Start\n");
    }

    // Sleep을 넣은이유는 스레드가 대기 가능 상태(alertable state)를 만족하게 만들기 위해서이다.
    // 대기 가능 상태가 아니면 foo를 호출하지 않음
    SleepEx(10000, TRUE);
 
    CloseHandle(hFile);
    _getch();
}

비동기 I/O 결과 얻기

#include <Windows.h>
#include <stdio.h>
#include <conio.h>

int main()
{
    HANDLE hFile = CreateFileA("C:\\Windows\\system32\\calc.exe", GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0);

    if(hFile == INVALID_HANDLE_VALUE)
    {
        printf("Error in Open File\n");
    }

    char buff[4096] = {0};
    OVERLAPPED ov = { 0 };
    ov.hEvent = CreateEvent(0, 0, 0, 0);

    BOOL ret = ReadFile(hFile, buff, 4096, 0, &ov);

    if(ret == FALSE)
    {
        if(GetLastError() == ERROR_IO_PENNDING)
            printf("Read Error\n");

        WaitForSingleObject(ov.hEvent, INFINITE);

        // 결과 조회
        // ov.Internal  -> error Code
        // ov.InternalHigh -> I/O 작업의 데이터 크기
        printf("Error : %d\n", ov.Internal);
        printf("Bytes : %d\n", ov.InternalHigh);

        // 이렇게 해도 바이트 확인 가능
        DWORD bytes = 0;
        GetOverlappedResult(hFile, &ov, bytes, TRUE);
    }
    else
    {
        printf("Comleting writes synchronosly\n");
    }

    CloseHandle(hFile);
    _getch();
}

비동기 I/O주의사항

int main()
{
    HANDLE hFile = CreateFileA("C:\\Windows\\system32\\calc.exe", GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0);

    if(hFile == INVALID_HANDLE_VALUE)
    {
        printf("Error in Open File\n");
    }

    {
        char buff[4096] = {0};
        OVERLAPPED ov = { 0 };
        ov.hEvent = CreateEvent(0, 0, 0, 0);

        BOOL ret = ReadFile(hFile, buff, 4096, 0, &ov);
    }
    
    // 여기로 올경우 buff, ov는 파괴된다.
    // ReadFile은 비동기로 동작중이니 잘못된 위치에 데이터를 쓰게 된다. 주의!
    

About Taehyung Kim

안녕하세요? 8년차 현업 C++ 개발자 김태형이라고 합니다. 😁 C/C++을 사랑하며 다양한 사람과의 협업을 즐깁니다. ☕ 꾸준한 자기개발을 미덕이라 생각하며 노력중이며, 제가 얻은 지식을 홈페이지에 정리 중입니다. 좀 더 상세한 제 이력서 혹은 Private 프로젝트 접근 권한을 원하신다면 메일주세요. 😎

Star
Useful Links