[CS] [Blocking I/O, Non-blocking I/O] X [Sync, Async]


웹서비스에서는 API 서비스를 개발 및 요청하다 보면 Sync/Async 개념을 접했을 것이다. 업무나 기능에 따라 무의식적으로 Blocking, Non-Blocking 를 처리했을 수도 있다.
금융권에서도 내부시스템간/대외계 간에 EAI나 MCI를 인터페이스를 통해서 요청하기 때문에 아래 4가지 구분으로 설정값으로 업무에 따라 구현되고 있다.

그 외 여러 기능에서 멀티프로세스, 멀티쓰레드 등 연관성이 있다.


여기서 I/O란? 데이터의 입력(Input)과 출력(Output)을 말한다. I/O는 파일 입출력뿐만 아니라 이기종간 네트워크를 통해서 데이터를 주고 받는 스트림들도 I/O라 칭한다.



1. Blocking I/O 와 Non-blocking I/O 설명

1-1. Blocking I/O

sync-blocking-s1


  • Blocking I/O 는 기본적으로 Synchronous 이다.
  • 어플리케이션에서 커널에게 read I/O를 요청하면
  • 커널에서 제어권을 가져감
  • 커널에서 read 작업이 끝나고 응답을 줄때까지 어플리케이션은 Block(기다림)
  • 그 동안 어플리케이션은 다른 작업을 못함
  • 작업 완료


1-2. Non-blocking I/O

sync-non-blocking-s1


  • 어플리케이션에서 커널에게 read I/O를 요청하지만
  • 어플리케이션에서 계속 제어권을 가지고 있음
  • 어플리케이션은 기다리지 않고(Non-Block) 커널괴 각자 자신의 작업을 처리함
  • 어플리케이션은 다른 작업을 하면서 시스템 콜을 보내서 커널에 요청한 I/O가 완료되었는지 물어봄
  • 작업 완료


2. [Blocking I/O, Non-blocking I/O] X [Sync, Async]

Blocking I/O 와 Non-blocking I/O 는 Async/Sync 따라 아래와 같이 4가지 케이스로 구분되어 있다.


  • BlockingNon-Blocking 
    SyncBlocking/SyncNon-Blocking/Sync
    AsyncBlocking/AsyncAIO(Non-Blocking/Async)
  • Bloking/Non-Blocking 구분 : 어플리케이션에서 다른 주체(커널 또는 타시스템)에 I/O를 요청하면, 요청된 다른 주체가 제어권을 가지는 여부이다.
  • Blocking I/O: 자신의 프로세스가 작업을 진행하다가 다른 주체에 I/O를 요청하면 요청받은 I/O가 제어권을 가져가고, I/O요청이 끝날때까지 기다렸다가 자신의 작업을 처리
  • Non-Blocking I/O : 자신의 프로세스가 다른 주체에 I/O를 요청해도, 자신의 프로세스가 제어권을 가지고 다른 주체의 작업과 상관없이 자신의 작업을 진행 하는것


Synchronous(동기): 작업을 요청하고 기다리다가 결과를 리턴해주면 바로 결과를 처리한다. Asynchronous(비동기): 작업을 요청하고 다른 작업을 하다가 결과를 리턴(Notification)해주면 콜백함수(를 통해서) 결과를 처리한다.(바로 처리하는 개념은 아님)



2-1. Sync Blocking I/O

sync-blocking-s1


  • Blocking : I/O 호출이 발생하면 커널의 I/O 작업이 완료될때까지 제어권을 커널이 소유, 자신의 프로세스는 I/O요청이 끝날 떄까지 다른 작업을 할 수 없다.
  • Sync : 커널 I/O 작업이 완료되면 해당 결과를 어플리케이션에서 리턴 받아서 직접 바로 처리한다.


SyncBlocking.java (Sync + Blocking)

public static void main(String[] args) throws Exception {
    System.out.println("## Application 클라이언트 요청 시작 ##");

    /* Sync(동기) 처리 역할: 콜백을 이용해서 타시스템 API 또는 프로세트 개념으로 생각 */
    //아래 콜백(커널(또는 B시스템))이 제어권을 가지기 때문에 Block
    Callee callee = new Callee();
    callee.setCallback(new Callee.CallBack() {
        @Override
        public void getApiBOK(Callee callee) {
            if (callee.getReturnYn() == false) {
                System.out.println("    커널(또는 B시스템)이 제어권 소유 - 요청/작업 등 대기 중");
            }
        }
    });
    callee.syncStart();


    System.out.println("커널(또는 B시스템)에서 작업요청 받은 결과 직접 바로 작업 처리");
    System.out.println("결과 값 (계좌번호): " + callee.getAccountNumber());

    System.out.println("---------------------------------------------");
    System.out.println("## Application 작업 완료 후에 클라이언트 요청 종료 ##");
    System.out.println("---------------------------------------------");
}

결과

## Application 클라이언트 요청 시작 ##
    커널(또는 B시스템)이 제어권 소유 - 요청/작업 등 대기 중
    커널(또는 B시스템)이 제어권 소유 - 요청/작업 등 대기 중
    커널(또는 B시스템)이 제어권 소유 - 요청/작업 등 대기 중
    커널(또는 B시스템)이 제어권 소유 - 요청/작업 등 대기 중
    커널(또는 B시스템)이 제어권 소유 - 요청/작업 등 대기 중
    커널(또는 B시스템)이 제어권 소유 - 요청/작업 등 대기 중
    커널(또는 B시스템)이 제어권 소유 - 요청/작업 등 대기 중
    커널(또는 B시스템)이 제어권 소유 - 요청/작업 등 대기 중
    커널(또는 B시스템)이 제어권 소유 - 요청/작업 등 대기 중
    커널(또는 B시스템)이 제어권 소유 - 요청/작업 등 대기 중
    커널(또는 B시스템)이 제어권 소유 - 요청/작업 등 대기 중
    커널(또는 B시스템)이 제어권 소유 - 요청/작업 등 대기 중
    커널(또는 B시스템)이 제어권 소유 - 요청/작업 등 대기 중
커널(또는 B시스템)에서 작업요청 받은 결과 직접 바로 작업 처리
결과 값 (계좌번호): 1002-45-123145
---------------------------------------------
## Application 작업 완료 후에 클라이언트 요청 종료 ##
---------------------------------------------



2-2. Async Blocking I/O

async-blocking-s1


  • Blocking : I/O 호출이 발생하면 커널의 I/O 작업이 완료될때까지 제어권을 커널이 소유, 자신의 프로세스는 I/O요청이 끝날 떄까지 다른 작업을 할 수 없다.
  • Async : 커널 I/O 작업이 완료되면 결과를 리턴(Noti)해주면 해당 결과를 콜백함수를 호출을 통해서 처리한다.(바로 처리하는 개념은 아님)


AsyncBlocking.java (Async Blocking I/O)

public static void main(String[] args) throws Exception {
    System.out.println("## Application 클라이언트 요청 시작 ##");

    /* Sync(동기) 처리 역할: 콜백을 이용해서 타시스템 API 또는 프로세스 개념으로 생각 */
    //아래 콜백(커널(또는 B시스템))이 제어권을 가지기 때문에 Block
    Callee callee = new Callee();
    callee.setCallback(new Callee.CallBack() {
        @Override
        public void getApiBOK(Callee callee) {
            if (callee.getReturnYn() == false) {
                System.out.println("    커널(또는 B시스템)이 제어권 소유 - 요청/작업 등 대기 중");
            } else {
                System.out.println("    커널(또는 B시스템)에서 작업요청 결과 콜백함수 호출로 처리(바로 처리는 안함)");
                System.out.println("    결과 값 (계좌번호): " + callee.getAccountNumber());
            }
        }
    });
    callee.syncStart();
    
    System.out.println("---------------------------------------------");
    System.out.println("## Application 작업 완료 후에 클라이언트 요청 종료 ##");
    System.out.println("---------------------------------------------");
}

결과

## Application 클라이언트 요청 시작 ##
    커널(또는 B시스템)이 제어권 소유 - 요청/작업 등 대기 중
    커널(또는 B시스템)이 제어권 소유 - 요청/작업 등 대기 중
    커널(또는 B시스템)이 제어권 소유 - 요청/작업 등 대기 중
    커널(또는 B시스템)이 제어권 소유 - 요청/작업 등 대기 중
    커널(또는 B시스템)이 제어권 소유 - 요청/작업 등 대기 중
    커널(또는 B시스템)이 제어권 소유 - 요청/작업 등 대기 중
    커널(또는 B시스템)이 제어권 소유 - 요청/작업 등 대기 중
    커널(또는 B시스템)이 제어권 소유 - 요청/작업 등 대기 중
    커널(또는 B시스템)이 제어권 소유 - 요청/작업 등 대기 중
    커널(또는 B시스템)이 제어권 소유 - 요청/작업 등 대기 중
    커널(또는 B시스템)이 제어권 소유 - 요청/작업 등 대기 중
    커널(또는 B시스템)이 제어권 소유 - 요청/작업 등 대기 중
    커널(또는 B시스템)이 제어권 소유 - 요청/작업 등 대기 중
    커널(또는 B시스템)에서 작업요청 결과 콜백함수 호출로 처리(바로 처리는 안함)
    결과 값 (계좌번호): 1002-45-123145
---------------------------------------------
## Application 작업 완료 후에 클라이언트 요청 종료 ##
---------------------------------------------



2-3. Sync Non-Blocking I/O

sync-non-blocking-s1


  • Non-Blocking : 커널의 I/O 작업을 요청해도 어플리케이션이 제어권을 소유하고, 어플리케이션과 커널은 각각 다른 작업을 할 수 있다.
  • Sync : 어플리케이션에서 커널 I/O 작업의 완료여부를 시스템 콜을 통해서 커널에게 묻는다. 해당 결과를 어플리케이션에서 리턴 받아서 직접 바로 처리하기 때문에 지속적으로 커널에게 물어본다.


SyncNonBlocking.java (Sync Non-Blocking I/O)

public static void main(String[] args) throws Exception {
    System.out.println("## Application 클라이언트 요청 시작 ##");

    /* 쓰레드와 콜백을 이용해서 타시스템 API 역할로 생각 */
    Callee callee = new Callee();
    callee.setCallback(new Callee.CallBack() {
        @Override
        public void getApiBOK(Callee callee) {
            //Nothing...
        }
    });
    callee.start();


    while (! callee.systemCall()) {//커널(또는 B시스템) 시스템 콜
        Thread.sleep(2000);
        if (callee.getReturnYn()) {
            System.out.println("커널(또는 B시스템)에서 작업요청 받은 결과 직접 바로 작업 처리");
            System.out.println("결과 값 (계좌번호): " + callee.getAccountNumber());
        } else {
            System.out.println("Application 제어권 소유 - 작업 진행 또는 커널(또는 B시스템) 결과 대기 중...");
        }
    }

    System.out.println("---------------------------------------------");
    System.out.println("## Application 작업 완료 후에 클라이언트 요청 종료 ##");
    System.out.println("---------------------------------------------");
}

결과

## Application 클라이언트 요청 시작 ##
    >> 커널(또는 시스템) 시스템 콜 (작업진행상태) [ 0 %]
Application 제어권 소유 - 작업 진행 또는 커널(또는 B시스템) 결과 대기 중...
    >> 커널(또는 시스템) 시스템 콜 (작업진행상태) [ 15 %]
Application 제어권 소유 - 작업 진행 또는 커널(또는 B시스템) 결과 대기 중...
    >> 커널(또는 시스템) 시스템 콜 (작업진행상태) [ 30 %]
Application 제어권 소유 - 작업 진행 또는 커널(또는 B시스템) 결과 대기 중...
    >> 커널(또는 시스템) 시스템 콜 (작업진행상태) [ 46 %]
Application 제어권 소유 - 작업 진행 또는 커널(또는 B시스템) 결과 대기 중...
    >> 커널(또는 시스템) 시스템 콜 (작업진행상태) [ 61 %]
Application 제어권 소유 - 작업 진행 또는 커널(또는 B시스템) 결과 대기 중...
    >> 커널(또는 시스템) 시스템 콜 (작업진행상태) [ 69 %]
Application 제어권 소유 - 작업 진행 또는 커널(또는 B시스템) 결과 대기 중...
    >> 커널(또는 시스템) 시스템 콜 (작업진행상태) [ 84 %]
커널(또는 B시스템)에서 작업요청 받은 결과 직접 바로 작업 처리
결과 값 (계좌번호): 1002-45-123145
    >> 커널(또는 시스템) 시스템 콜 (작업진행상태) [ 100 %]
---------------------------------------------
## Application 작업 완료 후에 클라이언트 요청 종료 ##
---------------------------------------------



2-4. Aync Non-Blocking I/O

async-non-blocking-s1


  • Non-Blocking : 커널의 I/O 작업을 요청해도 어플리케이션이 제어권을 소유하고, 어플리케이션과 커널은 각각 다른 작업을 할 수 있다.
  • Async : 커널 I/O 작업이 완료되면 결과를 리턴(Noti)해주면 해당 결과를 콜백함수를 호출을 통해서 처리한다.(바로 처리하는 개념은 아님)


AsyncNonBlocking.java (Aync Non-Blocking I/O)

public static void main(String[] args) throws Exception {
    System.out.println("## Application 클라이언트 요청 시작 ##");

    /* 쓰레드와 콜백을 이용해서 타시스템 API 역할로 생각 */
    Callee callee = new Callee();
    callee.setCallback(new Callee.CallBack() {
        @Override
        public void getApiBOK(Callee callee) {
            if (callee.getReturnYn() == false) {
                System.out.println("    커널(또는 B시스템) 요청/작업 등 대기 중");
            } else {
                System.out.println("    커널(또는 B시스템)에서 작업요청 결과 콜백으로 처리(바로 처리는 안함)");
                System.out.println("    결과 값 (계좌번호): " + callee.getAccountNumber());
            }
        }
    });
    callee.start();


    for (int i=10; i>0; i--) {
        System.out.println("Application 제어권 소유 - 작업 진행");
        try {
            Thread.sleep(1000);
        } catch (Exception e) {}
    }

    System.out.println("---------------------------------------------");
    System.out.println("## Application 작업 완료 후에 클라이언트 요청 종료 ##");
    System.out.println("---------------------------------------------");

}

결과

## Application 클라이언트 요청 시작 ##
Application 제어권 소유 - 작업 진행
    커널(또는 B시스템) 요청/작업 등 대기 중
Application 제어권 소유 - 작업 진행
    커널(또는 B시스템) 요청/작업 등 대기 중
Application 제어권 소유 - 작업 진행
    커널(또는 B시스템) 요청/작업 등 대기 중
Application 제어권 소유 - 작업 진행
    커널(또는 B시스템) 요청/작업 등 대기 중
    커널(또는 B시스템) 요청/작업 등 대기 중
Application 제어권 소유 - 작업 진행
Application 제어권 소유 - 작업 진행
    커널(또는 B시스템) 요청/작업 등 대기 중
    커널(또는 B시스템) 요청/작업 등 대기 중
Application 제어권 소유 - 작업 진행
Application 제어권 소유 - 작업 진행
    커널(또는 B시스템) 요청/작업 등 대기 중
    커널(또는 B시스템) 요청/작업 등 대기 중
Application 제어권 소유 - 작업 진행
Application 제어권 소유 - 작업 진행
    커널(또는 B시스템) 요청/작업 등 대기 중
    커널(또는 B시스템) 요청/작업 등 대기 중
---------------------------------------------
## Application 작업 완료 후에 클라이언트 요청 종료 ##
---------------------------------------------
    커널(또는 B시스템) 요청/작업 등 대기 중
    커널(또는 B시스템) 요청/작업 등 대기 중
    커널(또는 B시스템)에서 작업요청 결과 콜백으로 처리(바로 처리는 안함)
    결과 값 (계좌번호): 1002-45-123145



정리

Bloking/Non-Blocking

  • Bloking/Non-Blocking는 자신의 시스템(프로세스)이 다른 시스템(프로세스)에게 I/O를 요청할 때 누가 제어권을 가지는 여부이다.
  • Blocking I/O: I/O요청받은 시스템(프로세스)이 제어권을 가져가고, 요청이 끝날때까지 기다렸다가 자신의 시스템(프로세스)를 처리
  • Non-Blocking I/O : 자신의 시스템(프로세스)가 다른 시스템(프로세스)에 요청해도, 자신의 시스템(프로세스)가 제어권을 가지고 다른 시스템(프로세스)과 각자 작업을 처리


Sync/Async

  • Sync/Async는 호출되는 시스템(프로세스)의 작업 완료 여부를 누가 신경쓰냐가 관심사
  • 동기(sync): 호출되는 함수의 작업 완료 여부를 호출 하는 시스템(프로세스)가 신경씀
  • 비동기(async): 호출되는 함수의 작업 완료 여부를 호출 되는` 시스템(프로세스)가 신경씀




[참고]

  • https://etloveguitar.tistory.com/140
  • https://limdongjin.github.io/concepts/blocking-non-blocking-io.html#useful-references





[참고]

쓰레드와 콜백을 이용해서 편의상 타시스템 API(프로세스) 역할을 임의로 만든 것.

public class Callee extends Thread {

    CallBack callBack;

    Callee () {}

    private String accountNumber;
    private String msg;

    private int progressRate = 0;
    private Boolean returnYn = false;


    public String getAccountNumber() {
        return accountNumber;
    }

    public void setAccountNumber(String accountNumber) {
        this.accountNumber = accountNumber;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public int getProgressRate() {
        return progressRate;
    }

    public void setProgressRate(int progressRate) {
        this.progressRate = progressRate;
    }

    public Boolean getReturnYn() {
        return returnYn;
    }

    public void setReturnYn(Boolean returnYn) {
        this.returnYn = returnYn;
    }

    @FunctionalInterface
    public interface CallBack{
        public void getApiBOK(Callee callee);
    }

    public void setCallback(CallBack callBack) {
        this.callBack = callBack;
    }

    public boolean systemCall() {
        System.out.println("    >> 커널(또는 시스템) 시스템 콜 (작업진행상태) [ "+ this.getProgressRate() +" %]");
        return this.getReturnYn();
    }

    public Callee(CallBack callback) {
        this.callBack = callback;
    }

    /**
     * 비동기 API 역할 (하나의 쓰레드를 타시스템 API 또는 타프로세스 개념)
     */
    @Override
    public void run() {

        /* 커널(또는 B시스템) 작업 */
        for(int i=0;i<13;i++) {
            this.setReturnYn(false);
            this.setProgressRate( i * 100 / 13 );
            this.callBack.getApiBOK(this);

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        this.setReturnYn(true);
        this.setProgressRate(100);
        this.setAccountNumber("1002-45-123145");
        this.setMsg("조회 성공");
        this.callBack.getApiBOK(this);
    }

    /**
     * 비동기 API 역할
     */
    public void syncStart() {

        /* 커널(또는 B시스템) 작업 */
        for(int i=0;i<13;i++) {
            this.setReturnYn(false);
            this.setProgressRate( i * 100 / 13 );
            this.callBack.getApiBOK(this);

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        this.setReturnYn(true);
        this.setProgressRate(100);
        this.setAccountNumber("1002-45-123145");
        this.setMsg("조회 성공");
        this.callBack.getApiBOK(this);
    }
}