k8s 및 go 관련해서 사내 스터디를 진행 중입니다.

future 에 관해 스터디를 했었습니다.

 

책 저자는 future는 비동기 프로세스에 의해 생성되는 값에 플레이스홀더를 제공하는 연산자라고 합니다.(문맥에 따라 다른 의미가 될 수 있다고 항의하지 말라고 합니다.)

 

자바에서는 future를 통해 다른 작업을 하다가, 값이 필요해지면 get()을 해서 기다릴 수 있습니다.

아래 코드도 비슷하게 동작합니다.

slow future를 호출해서 본 작업을 실행하고, Result()를 호출하면, 결과가 올 때 까지 기다립니다.

 

package main

import (
	"context"
	"fmt"
	"sync"
	"time"
)

type Future interface {
	Result() (string, error)
}

type InnerFuture struct {
	once sync.Once
	wg   sync.WaitGroup

	res string
	err error

	resCh <-chan string
	errCh <-chan error
}

func (f *InnerFuture) Result() (string, error) {
	
    //SlowFunction이 끝날 때 까지 기다림. 그리고 한 번만 실행되도록 보장.
    f.once.Do(func() {
		f.wg.Add(1)
		defer f.wg.Done()
		fmt.Println("hi")
		fmt.Println(time.Now())
		f.res = <-f.resCh
		f.err = <-f.errCh
		fmt.Println("bye")
	})

	// 결과 값 가져올 때까지 기다리기.
	f.wg.Wait()
	fmt.Println("end")

	return f.res, f.err
}

func SlowFunction(ctx context.Context) Future {
	resCh := make(chan string)
	errCh := make(chan error)

	// 비동기 작업 실행.
	go func() {
		fmt.Println("start async(future job)")
		select {
		case <-time.After(time.Second * 5):
			fmt.Println("end async")
			resCh <- time.Now().String()
			errCh <- nil
		case <-ctx.Done():
			resCh <- ""
			errCh <- ctx.Err()
		}
	}()

	return &InnerFuture{resCh: resCh, errCh: errCh}
}

func main() {
	ctx := context.Background()

	//비동기 작업 실행
	future := SlowFunction(ctx)

	//다른 작업 수행
	fmt.Println("other work start")
	select {
	case <-time.After(time.Second * 3):
		fmt.Println("done")
	}

	//결과 가져오기.
	res, err := future.Result()
	res2, _ := future.Result()

	if err != nil {
		fmt.Println("error : ", err)
		return
	}
	fmt.Println(res)
	fmt.Println(res2)
}

출처 : https://github.com/cloud-native-go/examples (살짝 수정한 코드)

 

 

이를 실행하면, 

 

other work start
start async(future job)
done
hi
2009-11-10 23:00:03 +0000 UTC m=+3.000000001
end async
bye
end
end
2009-11-10 23:00:05 +0000 UTC m=+5.000000001
2009-11-10 23:00:05 +0000 UTC m=+5.000000001

 

이 반환 됩니다.

 

1,2 째줄은 동시에 수행이 됩니다.

3번 째줄은 3초 뒤에 출력이 됩니다.(기존 작업이 3초 sleep이기 때뮨에)

4,5번째 줄은 아까 값을 기다리는 로직이 있었습니다.(once.Do(...)) 이 부분의 로직을 호출했다는 의미로 hi와 호출 시간을 출력했습니다. 앞에서 3초 기다렸기 때문에, +3초 찍히는 걸 볼 수 있습니다.

6번째 줄은 future 내부의 실제 작업이 끝난 것을 의미하고, 7번째 줄은  once.Do(...) 에서 해당 작업이 끝난 것을 감지했을 경우 출력이 됩니다.

7,8 번 째 end가 두 번 출력된 이유는 main 함수에서 future.Result()를 2번 호출하고 있기 때문입니다. 하지만 6번째의 end async는 2번 호출되지 않은 것을 확인할 수 있습니다. 이는 sysn.once 를 이용하기 때문에 이러한 결과가 나왔습니다.

실제로 .Result() 를 처음 호출했을 경우는 future의 비동기 작업이 끝날 때 까지 기다리지만, 그 이후 호출에서는 한 번 더 작업을 실행하지 않고 이전에 처리한 결과값을 바로 리턴합니다.

 

그래서 9,10 번째도 똑같은 시간이 5초가 나온 것을 확인할 수 있습니다. 5초가 나온 이유는 future의 비동기 작업에서 5초 sleep했기 때문입니다.

 

 

비동기 프로그래밍이 진짜 어려운 것 같습니다. webflux 느낌도 나고 재미있네요.

반응형

'BackEnd > go' 카테고리의 다른 글

[Go] 리소스 상태 유지  (0) 2024.07.09
[GO] Rest API 구현  (0) 2024.07.06
[Go] struct 와 포인터(자바 클래스와 비교)  (0) 2024.06.25
[GO] go를 이용한 안정성 패턴 구현(서킷)  (0) 2024.06.12

+ Recent posts