보다 더 생생한 기록

[GO] once.Do() + 데드락 본문

Go

[GO] once.Do() + 데드락

viviviviviid 2023. 9. 8. 15:33

once.Do() 

go package 공식문서 중 sync 패키지 내에 있는 Once.Do 의 형태는 다음과 같다.

func (o *Once) Do (f func()) : f 함수를 한번만 실행

그 밑에 다음과 같은 코멘트가 붙어있다.

"no call to Do returns until the one call to f returns, if f causes Do to be called, it will deadlock."
: f 함수에 대한 한 번의 호출이 반환 될 때가지 Do 함수에 대한 호출이 반환되지 않습니다.

 

즉, 우리가 파라미터로 넘겨준 함수 f가 Do 함수를 다시 한번 호출하게 된다면 데드락이 발생 
Do 함수는 넘겨준 함수가 끝날때까지 종료되지 않기 때문

 

 

Code Review - DeadLock

이전에 작성했던 코드를 보면

func Blockchain() *blockchain {
	once.Do(func() {
		b = &blockchain{
			Height: 0,
		}
		checkpoint := db.Checkpoint()
		if checkpoint == nil {
			b.AddBlock()
		} else {
			b.restore(checkpoint)
		}
	})
	return b
}

여기서 Do를 사용하는 상황에서 DeadLock이 발생하고, 무한으로 즐기는 로딩창이 시작된다.

Do안의 코드가 참조한 메서드 AddBlock의 세부코드를 보면

func (b *blockchain) AddBlock() {
	block := createBlock(b.NewestHash, b.Height+1)
	b.NewestHash = block.Hash
	b.Height = block.Height
	b.CurrentDifficulty = block.Difficulty
	persistBlockchain(b)
}

이렇게 되어있는데 여기서 사용하는 createBlock를 한번 더 들어가 보면

func createBlock(prevHash string, height int) *Block {
	block := &Block{
		Hash:       "",
		PrevHash:   prevHash,
		Height:     height,
		Difficulty: difficulty(Blockchain()),
		Nonce:      0,
	}
	block.mine()
	block.Transaction = Mempool.TxToConfirm()
	block.persist()
	return block
}

위와 같다. 여기서 6번째 라인을 보면 value값에서 Blockchain()을 호출하고 있는 것이 보인다.

결국 한 바퀴 돌아와 맨 처음 Blockchain을 다시 부르는 것인데,

 

이는 아까 코멘트로 달려있던 "no call to Do returns until the one call to f returns, if f causes Do to be called, it will deadlock." 에 부합하다. 이래서 Deadlock이 발생한 것이었다.

 

Trouble Shooting

이를 해결하기 위해, createBlock의 형태를 변경하였다.

func createBlock(prevHash string, height int) *Block

에서

func createBlock(prevHash string, height, diff int) *Block

 

기존 difficulty(Blockchain())을 대체하는, diff는 Blockchain()을 호출하는 것이 아닌 다른 방식으로 끌고 오게 되면서 Deadlock이 해결되었다.