![싱글턴](/images/patterns/cards/singleton-mini.png?id=914e1565dfdf15f240e766163bd303ec)
Go로 작성된 싱글턴
싱글턴은 같은 종류의 객체가 하나만 존재하도록 하고 다른 코드의 해당 객체에 대한 단일 접근 지점을 제공하는 생성 디자인 패턴입니다.
싱글턴은 전역 변수들과 거의 같은 장단점을 가지고 있습니다: 매우 편리하나 코드의 모듈성을 깨뜨립니다.
싱글턴에 의존하는 클래스를 다른 콘텍스트에서 사용하려면 싱글턴도 다른 콘텍스트로 전달해야 합니다. 대부분의 경우 이 제한 사항은 유닛 테스트를 생성하는 동안 발생합니다.
개념적인 예시
일반적으로 구조체(struct)가 처음 초기화될 때 싱글턴 인스턴스가 생성됩니다. 이를 위해 구조체(struct)에 getInstance
메서드를 정의합니다. 이 메서드는 싱글턴 인스턴스를 생성하고 반환하는 책임을 질 것이며, 생성 후 getInstance
가 호출될 때마다 같은 싱글턴 인스턴스가 반환될 것입니다.
그러면 고루틴(goroutine)들은 어떻습니까? 싱글턴 구조체(struct)는 여러 고루틴들이 해당 인스턴스에 접근하려고 할 때마다 반드시 같은 인스턴스를 반환해야 합니다. 이 때문에 싱글턴 디자인 패턴을 잘못 구현하기 매우 쉽습니다. 아래 예시는 싱글턴을 생성하는 올바른 방법을 보여줍니다.
주목할 가치가 있는 몇 가지 사항들:
-
처음에
singleInstance
가 비어 있는지 확인하기 위한nil
검사가 있습니다. 이 검사는getinstance
메서드가 호출될 때마다 시간을 소모하는 잠금(lock) 작업을 방지하기 위한 것이며 이 검사가 실패하면singleInstance
필드가 이미 채워져 있음을 의미합니다. -
singleInstance
구조체(struct)는 잠금(lock) 내에서 생성됩니다. -
잠금(lock)을 획득한 후 또 다른
nil
확인이 있습니다. 이는 둘 이상의 고루틴이 첫 번째 검사를 우회하는 경우 하나의 고루틴만 싱글턴 인스턴스를 생성할 수 있도록 하기 위함입니다. 그렇게 하지 않으면 모든 고루틴들은 싱글턴 구조체(struct)의 자체 인스턴스들을 생성할 것입니다.
single.go: 싱글턴
package main
import (
"fmt"
"sync"
)
var lock = &sync.Mutex{}
type single struct {
}
var singleInstance *single
func getInstance() *single {
if singleInstance == nil {
lock.Lock()
defer lock.Unlock()
if singleInstance == nil {
fmt.Println("Creating single instance now.")
singleInstance = &single{}
} else {
fmt.Println("Single instance already created.")
}
} else {
fmt.Println("Single instance already created.")
}
return singleInstance
}
main.go: 클라이언트 코드
package main
import (
"fmt"
)
func main() {
for i := 0; i < 30; i++ {
go getInstance()
}
// Scanln is similar to Scan, but stops scanning at a newline and
// after the final item there must be a newline or EOF.
fmt.Scanln()
}
output.txt: 실행 결과
Creating single instance now.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
다른 예시
Go에서 싱글턴 인스턴스를 생성하는 다른 방법들이 있습니다:
-
init
함수
init
함수 내에서 단일 인스턴스를 생성할 수 있습니다. 이는 인스턴스의 초기 초기화가 괜찮은 경우에만 적용됩니다. init
함수는 패키지의 파일당 한 번만 호출되므로 단일 인스턴스만 생성될 수 있습니다.
-
sync.Once
sync.Once
는 작업을 한 번만 수행합니다. 아래 코드를 참조하세요:
syncOnce.go: 싱글턴
package main
import (
"fmt"
"sync"
)
var once sync.Once
type single struct {
}
var singleInstance *single
func getInstance() *single {
if singleInstance == nil {
once.Do(
func() {
fmt.Println("Creating single instance now.")
singleInstance = &single{}
})
} else {
fmt.Println("Single instance already created.")
}
return singleInstance
}
main.go: 클라이언트 코드
package main
import (
"fmt"
)
func main() {
for i := 0; i < 30; i++ {
go getInstance()
}
// Scanln is similar to Scan, but stops scanning at a newline and
// after the final item there must be a newline or EOF.
fmt.Scanln()
}
output.txt: 실행 결과
Creating single instance now.
Single instance already created.
Single instance already created.