Dev
go 언어에서 deep copy 사용
DevOps Engineer
2025. 3. 24. 08:02
728x90
Go 언어는 대부분 값 타입(value semantics)이 기본이지만, 슬라이스, 맵, 포인터, 구조체 포인터 등을 다루다 보면 얕은 복사(shallow copy)로 인한 예기치 않은 공유 문제가 발생할 수 있어요.
이럴 때 deep copy(깊은 복사)가 필요해집니다.
✅ 언제 Deep Copy가 필요한가?
🔹 1. 공유된 메모리로 인한 부작용을 피하고 싶을 때
- 슬라이스나 맵은 참조 타입이기 때문에 복사해도 같은 메모리 영역을 가리킵니다.
- 이를 수정하면 원본 데이터도 함께 변경되죠.
original := []int{1, 2, 3}
copy := original
copy[0] = 100
fmt.Println(original) // [100 2 3] ← 원본이 변경됨!
이런 부작용을 막기 위해서는 deep copy로 실제 데이터를 새롭게 복사해야 해요.
🔹 2. 다른 고루틴과 안전하게 데이터 분리하고 싶을 때
- 병행성 상황에서 공유된 맵/슬라이스를 동시에 쓰면 Race Condition이 발생할 수 있어요.
- 복사해서 쓰면 고루틴 간 충돌을 방지할 수 있습니다.
🔹 3. 입력 데이터 원본을 보존해야 하는 상황
- 함수나 API에서 입력 데이터를 수정하되, 원본 데이터를 나중에 다시 사용해야 할 때.
- 구조체가 중첩되거나 포인터 필드가 포함되어 있을 경우, 얕은 복사로는 부족합니다.
✅ Go에서 Deep Copy 구현 방식
Go에는 deepcopy() 같은 내장 함수는 없지만, 아래 방식으로 직접 구현할 수 있어요:
🔸 1. 슬라이스 복사
src := []int{1, 2, 3}
dst := make([]int, len(src))
copy(dst, src)
🔸 2. 맵 복사
original := map[string]int{"a": 1}
clone := make(map[string]int)
for k, v := range original {
clone[k] = v
}
🔸 3. 구조체 Deep Copy (수동)
type User struct {
Name string
Tags []string
}
func DeepCopyUser(u User) User {
tagsCopy := make([]string, len(u.Tags))
copy(tagsCopy, u.Tags)
return User{
Name: u.Name,
Tags: tagsCopy,
}
}
🔸 4. 구조체 복사 (범용 - JSON 활용)
import "encoding/json"
func DeepCopy[T any](src T) (T, error) {
var dst T
bytes, err := json.Marshal(src)
if err != nil {
return dst, err
}
err = json.Unmarshal(bytes, &dst)
return dst, err
}
※ 단점: 성능 느림, omitempty 영향, 인터페이스 필드는 제대로 복사 안 될 수 있음
🔍 참고: 실무에서 Deep Copy가 필요한 예시
- Redis 캐시에서 가져온 데이터를 수정할 때 원본을 보존해야 할 경우
- 상태(state)를 보존한 채 변경 이력을 관리하는 시스템 (예: Undo/Redo 기능)
- 백업 또는 스냅샷 기능을 구현할 때
✅ 요약
Go에서는 슬라이스, 맵, 포인터를 사용할 때 얕은 복사로 인해 공유 문제가 발생할 수 있고, 이를 방지하기 위해 deep copy가 필요합니다.
구현은 직접 해야 하며, 구조에 따라 copy(), 루프, JSON 활용 등의 방식으로 처리합니다.
728x90