본문 바로가기
자습

Go Thread-Safety : sync.Mutex, sync.Map

by litaro 2023. 8. 26.

Go 하면 떠오르는 단어 Goroutines

실제로 Go를 구현하다가 뭔가 동시에 해야한다 싶으면 "go" 만 적으면 되니... 얼마나 편한가.

Goroutine 관련 자료는 매우 많아서 잘 찾으면 된다.

 

최근에 궁금한건 Go Server로 들어온 특정 Data를 local file 에 deadlock 없이 잘 Read/Write 하는 방법~

만약 request handler에서 goroutine을 생성했다면 channel 을 활용하면 될것같은데, 난 단순히 go server에서 알아서 goroutine 생성하여 호출된 handler 에서 뭔가 local file 로 deadlock 없이 작업을 하고 싶었다. 

첫번째 접근) thread id 별로 file 을 append mode 로 만들면 되지 않을까?

그러기 위해서는 현재 호출된 handler의 Thread ID를 알아야 한다.

기본적으로 runtime.GOMAXPROCS 의 default가 runtime.NumCPU 니 thread 가 1개 이상은 될테니..

Q) handler가 실행된 goroutine이 어느 thread에서 돌고 있는지 알수 있을까? 

go에서 얘기하는 concurrency 는 하나의 thread에서도 가능한 컨셉인데. 당연히 알수 없겠지. 

알려면 runtime.LockOSThread 로 고정시키는 방법이 있겠지만, 그러면 성능이 떨어질것이다.

Q) 그럼 goroutine의 id를 알수 있을까?

의도적으로 go 에서는 goroutine id를 알수 없게 했다. https://go.dev/doc/faq#no_goroutine_id

 

Frequently Asked Questions (FAQ) - The Go Programming Language

Documentation Frequently Asked Questions (FAQ) Frequently Asked Questions (FAQ) Origins What is the purpose of the project? At the time of Go's inception, only a decade ago, the programming world was different from today. Production software was usually wr

go.dev

 

두번째 접근) 안전하게 그럼 Mutex 를 쓰자

결국 Mutex를 써서 Lock/Unlock을 해야할텐데. 

서버가 요청 몇개 받는것도 아니고... mutex 하나로 한다면 비효율적이지 않나?

여러 mutex를 사용해서 file 도 여러개 하면 좋을텐데.

Q) 요청을 잘 묶을 단위가 뭐가 있을까? 

Request API를 보다보니, remoteAddr IP:port 를 제공하고 있고 보통 port는 일정 pool을 가지고 사용할테니. 이걸로 활용.

Q) port 별 mutex 를 관리하고 싶은데 어떻게 할까?

sync.Map 을 사용

func readFile(port string) {
	mu := getFileMutex(port)
	mu.Lock()
	defer mu.Unlock()
	contents, _ := os.ReadFile(port)
	doSomething(contents)
}

func writeFile(port, data string) {
	mu := getMutex(port)
	mu.Lock()
	defer mu.Unlock()
	file, err := os.OpenFile(port, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer file.Close()
	_, err = file.Write([]byte(data))
	if err != nil {
		fmt.Println(err)
	}

}

var mutexes sync.Map

func getMutex(fileName string) *sync.Mutex {
	if mutex, ok := mutexes.Load(fileName); ok {
		return mutex.(*sync.Mutex)
	}

	mutex := &sync.Mutex{}
	mutexes.Store(fileName, mutex)
	return mutex
}

 local에서 Locust 로  3000 user 까지 테스트했을때 안정적으로 특별한 deadlock 문제 없이 동작 확인~ 

'자습' 카테고리의 다른 글

Spring Native  (0) 2022.05.14
go gRPC Server & gRPC Gateway  (0) 2021.09.20
Kong Gateway + Konga  (0) 2021.08.29
Jaeger with Go  (1) 2021.07.20
Zipkin Go  (0) 2021.07.17