함수 호출 관점에서 동기와 비동기 (3/31)

세젤게으름뱅이·2025년 3월 31일

Spring Webflux

목록 보기
2/16

정의

Caller : 호출하는 함수
Callee : 호출 당하는 함수

Functional Interface (함수형 인터페이스)

  • 함수형 프로그래밍을 지원하기 위해서 Java 8부터 도입
  • 1개의 추상 메소드를 갖고 있는 인터페이스
  • 함수를 1급 객체로 사용할 수 있다.
    • 함수를 변수에 할당
    • 함수를 인자로 전달
    • 함수를 반환값으로 사용
  • Function, Consumer, Supplier, Runnable . . .
  • 함수형 인터페이스를 구현한 익명 클래스를 람다식으로 변경 가능
@FunctionalInterface 
public interface Function<T, R> { 
	R apply(T t); 
}
  • 함수형 인터페이스는 호출한 Thread에서 실행된다.
public static void main(String[] args) {
    var consumer = getConsumer();
    consumer.accept(1);
    var consumerAsLambda = getConsumerAsLambda();
    consumerAsLambda.accept(1);
    handleConsumer(consumer);
}
public static Consumer<Integer> getConsumer() {
    Consumer<Integer> returnValue = new Consumer<Integer>() {
        @Override
        public void accept(Integer integer) {
            log.info("value in interface: {}", integer);
        }
    };
    return returnValue;
}
public static Consumer<Integer> getConsumerAsLambda() {
    return integer -> log.info("value in lambda: {}", integer);
}
public static void handleConsumer(Consumer<Integer> consumer) {
    log.info("handleConsumer");
    consumer.accept(1);
}

동기 & 비동기

동기

  • caller는 callee의 결과에 관심이 있다.
  • caller는 결과를 이용하여 다음 action을 수행한다.
  • caller는 callee를 이용하기 때문에 동기화
@Slf4j
public class A {
    public static void main(String[] args) {
        log.info("Start main");
        var result = getResult();
        var nextValue = result + 1;
        assert nextValue == 1;
        log.info("Finish main");
    }
    public static int getResult() {
        log.info("Start getResult");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        var result = 0;
        try {
            return result;
        } finally {
            log.info("Finish getResult");
        }
    }
}

비동기

  • caller는 callee의 결과에 관심이 없다.
  • callee는 결과를 이용하여 callback을 수행
  • caller는 callee를 이용하지 않기 때문에 동기화 X
  • getResult()는 결과를 이용해서 함수형 인터페이스 실행
@Slf4j
public class B {
    public static void main(String[] args) {
        log.info("Start main");
        getResult(new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) {
                var nextValue = integer + 1;
                assert nextValue == 1;
            }
        });
        log.info("Finish main");
    }
    public static void getResult(Consumer<Integer> cb) {
        log.info("Start getResult");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        var result = 0;
        cb.accept(result);
        log.info("Finish getResult");
    }
}

Blocking & non-Blocking

Blocking (동기 Blocking / 비동기 Blocking)

  • caller는 callee가 완료될 때까지 대기
  • 제어권을 callee가 가짐 (완료의 주체)
  • caller와 다른 별도의 thread가 필요 X (추가로 쓸 수도 있음)
  • 방식의 차이는 존재
    • A는 getResult의 결과를 돌려받기 전까지 아무것도 X (동기 Blocking)
    • B는 getResult가 결과를 구하고, callback을 실행하기 전까지 아무것도 X (비동기 Blocking)

non-Blocking (동기 non-Blocking)

  • caller는 callee를 기다리지 않고, 본인의 일을 진행
  • 제어권을 caller가 가짐 (본인의 일 진행 가능하므로)
  • caller와 다른 별도의 thread가 필요 O (기다리지 않으려면 작업을 위임)
  • 방식의 차이
    • A는 getResult가 완료되기 전까지는 caller는 자신의 일 수행 X (동기 Blocking)
    • C는 getResult가 완료되지 않더라도, caller는 자신의 일 수행 O (동기 non-Blocking)
  • C의 케이스는 "동기 = Blocking" 에 대한 반례?이다.
    • 동기는 결과 값을 기다리고, Blocking은 callee의 결과를 기다리니 개념상 비슷하게 이해될 수도 있지만, 반례가 존재한다.
    • 동기 non-Blocking은 기다리면서 main Thread가 작동할 수 있다.
  • main()
    • getResult()을 호출하면 내부에서 선언한 Single Thread는 물리적으로 1000ms를 sleep() 한다.
    • 하지만 main Thread와는 별개이기 때문에, 1000ms를 기다리는 동안 main Thread는 getResult()의 'result'가 return되기 전까지 while loop에서 100ms씩 10번을 카운팅 할 것이다.
    • result가 return 된다면, 생성한 Single Thread를 shutdown하고, main도 작업을 마친다.
public static void main(String[] args) throws InterruptedException, ExecutionException {
    log.info("Start main");
    var count = 1;
    Future<Integer> result = getResult();
    while (!result.isDone()) {
        log.info("Waiting for result, count: {}", count++);
        Thread.sleep(100);
    }
}
var nextValue = result.get() + 1; 
assert nextValue == 1;
        log.info("Finish main");
  • getResult()
public static Future<Integer> getResult() {
    var executor = Executors.newSingleThreadExecutor();
    try {
        return executor.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                log.info("Start getResult");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                var result = 0;
                try {
                    return result;
                } finally {
                    log.info("Finish getResult");
                }
            }
        });
    } finally {
        executor.shutdown();
    }
}

non-Blocking (동기 non-Blocking)

  • caller는 callee의 결과에 관심이 없다. callback 작업까지 알아서 수행하니까.
  • getResult 호출 후, getResult가 완료되지 않더라도, main은 본인의 작업을 수행
  • 비동기 non-Blocking

결론

  • 동기 Blocking : caller 중단 / 결과를 얻고 직접처리
  • 비동기 Blocking : caller 중단 / callee가 처리 (callback)
  • 동기 non-Blocking : caller 할 일함 / 결과를 얻고 직접처리
  • 비동기 non-Blocking : caller 할 일함 / callee가 처리 (callback)
profile
🤦🏻‍♂️

0개의 댓글