내가 개발을 하면서 겪은 멍청한 실수들 (1)
2024. 2. 27. 03:12ㆍ개발!
1. go언어에서 range문으로 배열의 값을 가지고 올 수 있다. 이 값 변수는 재사용된다. 코드 몇 줄 줄여보려다가 큰일날뻔 했다.
m := map[string]*string{}
arr := []string{"a", "b", "c"}
for i, v := range arr {
m[v] = &v // X (v 변수는 재사용된다. 따라서 모든 map 값에 같은 주소가 들어간다.
m[v] = &arr[i] // O
}
2. go에서 map은 여러 고루틴에서 접근할 경우 항상 lock을 걸어야 한다. 따라서 편하게 사용하려고 따로 struct를 만들어서 사용했다.
// 문제가 된 코드
type Map[K comparable, V any] struct {
sync.RWMutex
m map[K]V
}
func (m *Map[K, V]) ForEach(fn func(k K, v V) bool) { // ForEach with read lock
m.RLock()
defer m.RUnlock()
for k, v := range m.m {
if fn(k, v) {
break
}
}
}
func (m *Map[K, V]) ForEachLock(fn func(k K, v V) bool) { // ForEach with write lock
m.Lock()
defer m.Unlock()
for k, v := range m.m {
if fn(k, v) {
break
}
}
}
func (m *Map[K, V]) Delete(key K) {
m.Lock()
defer m.Unlock()
delete(m.m, key)
}
여기서 문제가 된 함수가 이 두개인데, 내가 ForEach에 전달하는 함수에서 Delete를 사용했다. 그래서 read lock, write lock이 겹치면서 deadlock이 발생하게 되었는데 정말 특수한 경우에만 이게 발동되어서 찾는데 오래걸렸다. 이 문제를 다 해결할때쯤 알게되었는데 특정 상황 발생 5분 후부터 deadlock이 걸렸다.
결국 pprof를 사용해서 지금 실행중인 goroutine을 확인해봤는데 9만개가 넘는 goroutine이 특정 함수에 멈춰있었고 그 함수를 집중해서 보니 deadlock이 발생하는 것을 확인했다. 따라서 ForEachLock 함수를 사용하고 직접 map에 접근해서 지우는 방법으로 해결했다.
'개발!' 카테고리의 다른 글
ScriptConverter 제작 (0) | 2019.03.31 |
---|