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 |