관리 메뉴

피터의 개발이야기

[GO] Go 언어에서의 "fatal error: concurrent map read and map write" 해결하기 본문

Programming/GO

[GO] Go 언어에서의 "fatal error: concurrent map read and map write" 해결하기

기록하는 백앤드개발자 2025. 2. 15. 09:37
반응형

ㅁ 들어가며

Go 개발자라면 한 번쯤 마주칠 수 있는 "fatal error: concurrent map read and map write" 오류에 대해 정리하였다. 이 오류는 동시성 프로그래밍에서 흔히 발생하는 문제로, 여러 고루틴이 동시에 맵을 읽고 쓰려고 할 때 발생한다.

 또한, -race 플래그를 사용하면 Go의 레이스 디텍터로 프로그램을 테스트하면 이러한 동시성 문제를 사전에 발견하는 데 도움이 될 수 있다. 이 부분은 [GO] Go Race Detector: 동시성 버그를 잡아내는 강력한 도구에 정리하였다.

 

ㅁ 오류의 원인

 이 오류는 Go의 맵이 기본적으로 동시성에 안전하지 않기 때문에 발생한다. 여러 고루틴이 동시에 같은 맵에 접근하여 읽기와 쓰기 작업을 수행할 때, Go 런타임은 이를 감지하고 프로그램을 중단시킨다.

 

ㅁ 해결 방법

이 문제를 해결하기 위한 몇 가지 방법을 찾아보았다.

sync.Mutex 사용

가장 간단한 방법은 sync.Mutex를 사용하여 맵에 대한 접근을 동기화하는 것이다.

var mu sync.Mutex
m := make(map[string]int)

// 쓰기 작업
mu.Lock()
m["key"] = value
mu.Unlock()

// 읽기 작업
mu.Lock()
value := m["key"]
mu.Unlock()

 

sync.RWMutex 사용

읽기 작업이 많은 경우, sync.RWMutex를 사용하여 성능을 개선할 수 있다.

var mu sync.RWMutex
m := make(map[string]int)

// 쓰기 작업
mu.Lock()
m["key"] = value
mu.Unlock()

// 읽기 작업
mu.RLock()
value := m["key"]
mu.RUnlock()

 

sync.Map 사용

Go 1.9 이후 버전에서는 sync.Map을 사용할 수 있다. 이는 동시성에 안전한 맵 구현을 제공한다.

var m sync.Map

// 쓰기 작업
m.Store("key", value)

// 읽기 작업
value, ok := m.Load("key")

 

채널 사용

맵 접근을 단일 고루틴으로 제한하고 채널을 통해 통신하는 방법도 있다.

type SafeMap struct {
    c chan command
    m map[string]int
}

func NewSafeMap() *SafeMap {
    sm := &SafeMap{
        c: make(chan command),
        m: make(map[string]int),
    }
    go sm.run()
    return sm
}

func (sm *SafeMap) run() {
    for cmd := range sm.c {
        switch cmd.action {
        case "write":
            sm.m[cmd.key] = cmd.value
        case "read":
            cmd.result <- sm.m[cmd.key]
        }
    }
}

 

 

ㅁ Mutext와 sync.Map을 사용한 샘플코드

package main

import (
    "fmt"
    "sync"
)

func main() {
    // Mutex를 사용한 방법
    var counter = struct {
        sync.RWMutex
        m map[string]int
    }{m: make(map[string]int)}

    // 읽기
    counter.RLock()
    n := counter.m["some_key"]
    counter.RUnlock()
    fmt.Printf("Mutex - Read value: %d\n", n)

    // 쓰기
    counter.Lock()
    counter.m["some_key"]++
    counter.Unlock()

    // sync.Map을 사용한 방법
    var syncMap sync.Map

    // 저장
    syncMap.Store("some_key", 1)

    // 읽기
    value, ok := syncMap.Load("some_key")
    if ok {
        fmt.Printf("sync.Map - Read value: %v\n", value)
    }

    // 쓰기 (읽고 쓰기)
    syncMap.Range(func(key, value interface{}) bool {
        if k, ok := key.(string); ok && k == "some_key" {
            if v, ok := value.(int); ok {
                syncMap.Store(k, v+1)
            }
        }
        return true
    })

    // 결과 확인
    value, _ = syncMap.Load("some_key")
    fmt.Printf("sync.Map - Updated value: %v\n", value)
}

 

 

ㅁ 마무리

  동시성 프로그래밍에서 맵을 안전하게 사용하는 것은 중요하다. 상황에 따라 적절한 동기화 메커니즘을 선택하여 사용해야 한다.

  동시성 문제를 해결할 때는 성능과 코드의 복잡성을 고려해야 한다. sync.Mutexsync.RWMutex는 간단하지만 성능 저하가 있을 수 있고, 채널을 사용한 방식은 성능은 좋지만 코드가 복잡해질 수 있다. 프로젝트의 요구사항에 맞는 최적의 솔루션을 선택하는 것이 중요하다.

 

ㅁ 함께 보면 좋은 사이트

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

Go 언어 개발의 동시성 및 동기화 문제를 해결하는 방법

  ㄴ  뮤텍스 잠금, 채널 및 원자적 작업으로 동시성 해결방법 제시

[Golang] fatal error: concurrent map writes

  ㄴ 동시성 문제를 샘플코드로 Mutex, 채널이 코드 간결성과 성능의 차이를 설명함

반응형
Comments