본문 바로가기

Developer/Go

Golang defer 실행 순서

Go를 사용 중 문득 든 궁금증이 defer 함수를 여러 개 사용한다면 실행 순서가 어떻게 되는지 궁금했다.

기본적으로는 호출한 순서대로 실행될 것 같았고 고루틴의 경우는 예외적으로 랜덤하게 실행될 것으로 생각되어 실험을 해보았다.

 

 

테스트

기본 사용 시

package main

import (
	"log"
	"time"
)

// 3초 후 숫자 프린트
// 결과: 3, 2, 1
func printNo() {
	defer log.Println("printNo=1")
	defer log.Println("printNo=2")
	defer log.Println("printNo=3")

	log.Println("run defer functions after 3 seconds")

	time.Sleep(time.Duration(3) * time.Second)
}

// 3초 후 숫자 프린트
// 결과: 2, 3, 1
func printNo2() {
	defer log.Println("printNo=1")
	defer log.Println("printNo=3")
	defer log.Println("printNo=2")

	log.Println("run defer functions after 3 seconds")

	time.Sleep(time.Duration(3) * time.Second)
}

func main() {
	log.Println("run defer order")

	// print number test
	printNo()
	printNo2()
}

 

결과를 보았을 때 "마지막으로 호출한 순서"대로 실행되는 것을 확인하였다.

따라서 기본 동작은 호출한 순서의 역순으로 실행된다고 볼 수 있다.

 

defer 내부에 고루틴 호출 시

당연한 결과겠지만 defer 내부에 고루틴을 여러 개 호출하는 경우, 랜덤하게 실행되는 것을 확인할 수 있었다.

여기서 주의할 점은 Go 1.21 이하 버전에서는 for-loop 문에서 클로저(Closure)를 사용할 경우, 클로저에서 받는 변수의 값이 고정된 값으로 받는 현상이 있다. 이는 Go에서 고루틴이 실행되기 전에 for-loop 문이 모두 실행되어 발생하는 현상으로 Go 1.22 이상에서 수정될 예정이라고 한다. 이 부분에 대해서는 나중에 따로 다뤄보려고 한다. (reference: Fixing For Loops in Go 1.22 - The Go Programming Language, Frequently Asked Questions (FAQ) - The Go Programming Language)

package main

import (
	"log"
	"time"
)

// 3초 후 숫자 프린트
// 결과: 3, 2, 1
func printNo() {
	defer log.Println("printNo=1")
	defer log.Println("printNo=2")
	defer log.Println("printNo=3")

	log.Println("run defer functions after 3 seconds")

	time.Sleep(time.Duration(3) * time.Second)
}

// 3초 후 숫자 프린트
// 결과: 2, 3, 1
func printNo2() {
	defer log.Println("printNo=1")
	defer log.Println("printNo=3")
	defer log.Println("printNo=2")

	log.Println("run defer functions after 3 seconds")

	time.Sleep(time.Duration(3) * time.Second)
}

// 고루틴 지연 실행
// 결과: 랜덤
func goroutineDefer() {
	// use i in for-loop may not properly work on closure with goroutine due to bug in golang before 1.22
	// this bug will be fixed in 1.22 (ref: https://go.dev/blog/loopvar-preview, https://go.dev/doc/faq#closures_and_goroutines)
	for i := 0; i <= 4; i++ {
		j := i
		defer func() {
			go log.Printf("goroutine defer exit=%d\n", j)
		}()
	}

	log.Println("run goroutine in defer functions after 3 seconds")
	time.Sleep(time.Duration(3) * time.Second)
}

func main() {
	log.Println("run defer order")

	// print number test
	printNo()
	printNo2()
	
	// goroutine defer
	goroutineDefer()
	time.Sleep(time.Duration(1) * time.Second)
}

 

샘플 코드

meaningful-go/defer-order at main · torrang/meaningful-go (github.com)

 

결론

  • 여러 개의 defer 함수 호출 시 호출한 순서의 역순으로 실행된다.
  • defer 함수안에 여러 고루틴이 있는 경우, 당연히 실행 순서가 보장되지 않는다.