공대생 정리노트

Map : concurrent 상황일 때 Read도 RLock을 해야 하는 이유 본문

언어/Go

Map : concurrent 상황일 때 Read도 RLock을 해야 하는 이유

woojinger 2022. 2. 18. 00:49

레퍼런스


문제 상황

상황을 한번 가정해보자.

아무 key-value도 가지지 않는 Map을 가지고 있다고 해보자.

이 Map에 수많은 고루틴들이 랜덤으로 key를 넣어 Read를 시도한다.

만약 value가 있으면 그대로 가져오고, value가 없다면 랜덤으로 만든 value를 key-value 쌍으로 만들어 Map에 넣는다.

 

즉 각 고루틴은

1. Read를 시도

2. 없으면 Write

을 반복한다.

 

그렇다면 여기서 Write을 할 때는 Lock을 거는 것이 확실하다. 그런데 Read를 할 때도 Lock을 걸어야 할까?

즉, 한 고루틴이 Map[0]을 읽고 있는데 동시에 다른 고루틴이 Map[1]에 value를 write을 하는 것이 영향을 끼칠 수 있을까?

Map의 구조

Go Map은 채널이나 슬라이스보다 훨씬 복잡하다.

단순히 backing array를 보는 것이 아니라 internal state나 map iterator 등을 가지고 있다.

Map은 reference variable이 아닌 runtime.hmapstructure에 대한 포인터이다.

https://dave.cheney.net/2018/05/29/how-the-go-runtime-implements-maps-efficiently-without-generics

HashMap의 load factor가 일정 수준을 증가하면 HashMap의 성능이 줄어든다.

 

그래서 Go의 빌트인 자료구조들 중 오직 Map만 data를 내부적으로 움직인다.

Map은 O(1)을 보장하기 위해 insert나 delete시 리밸런스를 한다.

이러한 이유 때문에 Map의 value는 addressable하지 않다. (addressable이란? https://go.dev/ref/spec#Address_operators)

 

예를 들어 Map[1] = 2를 write하고, a = &Map[1]로 할당했다고 하자. 그럼 우리는 *a가 2의 값을 가질 것이라 생각할 것이다. 그러나 리밸런스가 되면 이것이 2를 가리키지 않을 수 있다는 것이다. Map[1]은 2를 여전히 가리키고 있겠지만.

그렇다면 어떻게 써야할까?

1. RWMutex를 이용하여 Map에서 Read를 할 때 RLock을 걸자

2. go 1.9버전 이후에는 sync.Map을 이용할 수 있다. 이걸 사용하자

 

Comments