'다시 공부하는 수학 > 모두를 위한 선형대수학' 카테고리의 다른 글
(edwith) 칸아카데미, 모두를 위한 선형대수학 1주차 (0) | 2021.06.13 |
---|
(edwith) 칸아카데미, 모두를 위한 선형대수학 1주차 (0) | 2021.06.13 |
---|
김영한 개발자님의 "모든 개발자를 위한 HTTP 웹 기본 지식"
인프런, https://www.inflearn.com/course/http-%EC%9B%B9-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC/dashboard
소개 ~ HTTP 메서드 활용
이번 주차는 선형대수학의 벡터에 대해서 공부하였다.
벡터는 Magnitude 와 direction을 가진다.
단위 벡터 unit vector는 한 direction으로 향하는 magnitude가 1인 벡터이다.
(edwith) 칸아카데미, 모두를 위한 선형대수학 2주차 (0) | 2021.07.04 |
---|
확률과 통계, 선형대수학, 미적분을 다시 공부하기로 마음 먹었다.
내년 초에는 방송통신대학교 통계학과에 도전하려고 한다.
멀티 스레드 모델로는 커널 스레드, 사용자 스레드, 멀티 레벨 스레드가 있다. 커널 스레드는 커널이 직접 생성하고 관리하는 스레드이다. 사용자 스레드는 라이브러리로 구현된 일반 스레드이다. 사용자 스레드와 커널 스레드를 사용하기 위해서는 시스템 호출로 커널 기능을 사용해야 한다. 이때의 대응 방식에 따라 모델이 분류된다.
여러 스레드가 커널 스레드 하나와 연결된 방식이다. 운영체제가 멀티스레드를 지원하지 않을 때 사용한 방법이다. 이 방법에서 스레드는 사용자 레벨에서 라이브러리를 사용해 구현되고 라이브러리는 커널 기능을 대신한다. 커널은 사용자 레벨 스레드를 프로세스 하나로 본다.
사용자 레벨 스레드는 여러 스레드를 작동하지만 한 커널 스레드와만 연결된다. 라이브러리가 커널 기능을 대신하기 때문에 문맥 교환이 필요하지 않아 속도가 빠르다. 하지만 여러 스레드가 한 커널 스레드와 연결되기 때문에 커널 스레드가 대기 상태에 들어가면 모든 사용자 스레드가 대기해야 한다. 또한 한 프로세스의 타임 슬라이스(쿼텀)를 여러 스레드가 공유하는 방식이라 멀티코어를 사용할 수 없다. 그리고 커널 레벨에서처럼 공유 변수를 보호하지 않아 보안에 취약하다.
커널 레벨 스레드는 커널이 멀티스레드를 지원하는 방식이다. 한 사용자 스레드에 한 커널 스레드가 연결된다. 한 커널 스레드가 대기 상태가 되어도 다른 커널 스레드에 부착된 스레드는 작업을 계속한다. 그리고 커널 레벨이 공유 변수를 보호해 보안에 취약하지 않으며 커널 기능을 모두 사용한다. 그리고 멀티 코어를 사용하여 효율적이지만 문맥 교환을 할 때 오버헤드가 발생해 느리다.
하이브리드 스레드라고도 불리며 커널 스레드 개수가 사용자 스레드보다 같거나 적다. 한 커널 스레드가 대기 상태가 되면 다른 커널 스레드가 작업을 대신해 사용자 레벨 스레드보다 효율적으로 작업을 처리한다. 하지만 커널 레벨 스레드에서 발생하는 문맥 교환에 따른 오버헤드를 가져 속도가 저하된다. 그래서 속도가 중요할 때는 사용자 레벨 스레드로 작동하고 보안과 커널 기능이 중요할 때는 커널 레벨 스레드로 작동한다.
Golang만 멀티 스레딩을 지원하는 것이 아니라 JAVA, C++ 등 다양한 프로그래밍 언어에서 멀티 스레딩을 지원합니다. JAVA 스레드와 고루틴을 비교하면
JAVA 스레드 | Golang 고루틴 | |
스택 공간 | 1MB + Guard Page | 2KB |
문맥 교환 비용 | 큼 | 적음 |
생성 및 삭제 비용 | 큼 | 적음 |
고루틴은 멀티 레벨 스레드 모델을 사용합니다. 이 모델은 구현이 어렵지만 Golang은 이를 지원함으로써 효율적인 멀티 스레딩을 가능하게 하였습니다.
참조
고루틴은 어떻게 작동하는가?
stonzeteam.github.io/How-Goroutines-Work/
Goroutines vs Threads
tech.ssut.me/goroutine-vs-threads/
도서
쉽게 배우는 운영체제 chapter03 프로세스와 스레드
Go 에서 싱글톤 패턴은 어떻게 작동할까(번역) (0) | 2021.03.21 |
---|---|
Golang, sync.Pool 디자인 이해하기(번역) (0) | 2021.03.21 |
Sort 정렬 인터페이스 구현 (0) | 2021.02.21 |
medium.com/golang-issue/how-singleton-pattern-works-with-golang-2fdd61cd5a7f
저는 항상 싱글톤 패턴을 제 프로젝트에서 구현해야 하지만, Golang에는 우리가 처리해야 할 몇 가지 특이한 점이 있습니다. 이 기사에서는 Golang을 사용하여 Singleton을 구현하는 두 가지 방법, 즉 "Not Thread Safe" 양식과 "Thread Safe" 양식을 제시하겠습니다. 목표는 구현 형태와 싱글톤 패턴을 구현해야 하는 시기를 실용적이고 기술적인 방식으로 제시하는 것입니다.
데이터베이스에 대한 연결을 열고 단일 인스턴스에 보관해야 하므로 데이터베이스에 과부하가 걸리지 않고 동시 연결의 한계가 초과되거나 코드가 최적화되는 문제가 발생하지 않습니다. 연결에는 계산 비용이 매우 많이 든다는 것을 알고 있습니다.
기본적으로 풀링(pooling)을 하려면 애플리케이션이 통신하기 위해 여는 모든 연결을 제어할 필요가 있습니다. 그래서 우리가 실행하는 모든 동작에서 선택, 삭제, 삽입 또는 갱신은 원자 형태의 메모리에 Singleton 패턴을 사용할 것입니다. Singleton 비동기 및 동기화 연결의 예입니다.
연결 풀링 문제를 해결하기 위해 Singleton 패턴을 사용하여 솔루션을 구현하기 전에 Singleton 패턴을 사용해야 하는 몇 가지 다른 예를 살펴보겠습니다.
다음은 전역 변수를 보호하기 위한 몇 가지 방법의 예입니다.
// type global
type singleton map[string]string
var instance singleton
func NewClass() singleton {
if instance == nil {
instance = make(singleton) // <-- not thread safe
}
return instance
}
var lock = &sync.Mutex{}
// type global
type singleton map[string]string
var instance singleton
func NewClass() singleton {
lock.Lock()
defer lock.Unlock()
if instance == nil {
instance = make(singleton) // <-- thread safe
}
return instance
}
var once sync.Once
// type global
type singleton map[string]string
var instance singleton
func NewClass() singleton {
once.Do(func() { // <-- atomic, does not allow repeating
instance = make(singleton) // <-- thread safe
})
return instance
}
위에서는 동기식 솔루션 예시 1 과 비동기식 솔루션 예시 2와 3이 있습니다. 예제 3은 인스턴스가 원자성이 되어 한 번에 선언되도록 함으로써 "race condition"으로부터 보호하는 가장 좋은 솔루션입니다.
이 기사의 목적은 전역 변수를 어떻게 보호하려고 하는지 정확하게 보여주기 위한 것입니다. Golang에서 우리는 이것을 하는 여러 가지 방법을 가지고 있습니다.
Golang을 사용한 싱글톤 패턴의 구현을 위한 가능한 해결책을 생각할 때, Goroutines와 마주치게 됩니다. 고루틴은 코드를 비동기적으로(asynchronously) 그리고 동시적으로(concurrently) 실행하도록 합니다. 그리고 고루틴을 애플리케이션에 사용할 때 모든 사고 방식과 구현 방식을 변화해야 했습니다. 애플리케이션은 더이상 동기적인 애플리케이션이 아니었습니다.
고루틴은 강력한 자원이며 올바르게 사용되면 일상적인 전투를 벌일 수 있는 강력한 동맹이 됩니다. 경쟁을 사용하는 코드를 구현할 때마다 우리는 우리가 다루어야 하는 몇몇 알려진 시나리오들을 가지고 있습니다, 프로그램 범위, 전역 변수들, 매개 변수 전달, 포인터들 등 이 모든 것들이 정확하고 최적화된 방식으로 경쟁과 함께 일할 수 있도록 처리되어야 합니다.
경쟁을 사용하여 발생할 수 있는 문제의 좋은 예는 전역 변수를 사용하는 것입니다. Singleton Pattern의 구현과 가능한 솔루션이 경쟁의 사용을 수용할 수 있도록 작성됩니다. Golang에서 작성된 프로그램의 런타임에 발생할 수 있는 스텔스 버그를 완화하기 위해 좋은 프로그래밍 방식을 사용하여 코드를 작성하겠습니다.
싱글톤 패턴의 설명은 다음과 같습니다:
"싱글톤은 소프트웨어 디자인 표준입니다. 이 표준은 클래스의 개체에 대한 전역 접근 지점을 유지하면서 클래스의 인스턴스 하나만 존재하도록 보장합니다."
싱글톤은 객체로의 인스턴스화를 제한하는 설계 패턴입니다. 한 번만 발생하도록 보장해야 합니다.
기본적으로 싱글톤은 전역 변수를 사용하는 방법입니다. 우리는 전역 변수의 사용이 얼마나 위험한지 알고 있습니다. 우리의 코드는 전역 변수의 접근에 취약하거나 시스템의 어느 부분에서든 그 값을 바꿀 수 있습니다. 그래서 프로그램을 디버그하려고 할 때, 어떤 코드 경로가 현재 상태로 이어지는지를 알아내는 것은 쉬운 일이 아닐 것입니다. 그래서 싱글톤 패턴을 안티 패턴으로 여기지 않고, 전역 변수를 보호하는 방법으로 간주합니다.
생략된 부분이 많고 번역도 미흡하지만 요약하자면
1. singleton 패턴은 전역변수를 보호하는 방법이다.
2. goroutine 에서 전역변수에 접근해야 하는 경우가 생긴다.
3. mutex, atomic, sync.Once을 Golang이 지원한다.
4. Once 가 가장 효율적인 방법이다.
멀티 스레드 모델과 고루틴 (0) | 2021.04.05 |
---|---|
Golang, sync.Pool 디자인 이해하기(번역) (0) | 2021.03.21 |
Sort 정렬 인터페이스 구현 (0) | 2021.02.21 |
medium.com/a-journey-with-go/go-understand-the-design-of-sync-pool-2dde3024e277
이 기사는 Go 1.12와 1.13 버전을 기반으로 작성되었고 두 버전 사이의 sync/pool.go 변화를 설명합니다.
sync 패키지는 가비지 컬렉터(GC) 압력을 줄이기 위해 다시 사용할 수 있는 강력한 인스턴스 풀을 제공합니다. 패키지를 사용하기 전에 애플리케이션을 벤치마킹하는 것이 매우 중요합니다. 내부에서 작동하는 방식을 잘 이해하지 못하면 성능이 저하될 수 있기 때문입니다.
1k 할당으로 매우 간단한 환경에서 작동하는 방식을 살펴보는 기본적인 예를 들어 보겠습니다.
package main
import (
"sync"
"testing"
)
type Small struct {
a int
}
var pool = sync.Pool{
New: func() interface{} { return new(Small) },
}
func inc(s *Small) {
s.a++
}
func BenchmarkWithoutPool(b *testing.B) {
var s *Small
for i := 0; i < b.N; i++ {
for j := 0; j < 10000; j++ {
s = &Small{a: 1}
b.StopTimer()
inc(s)
b.StartTimer()
}
}
}
func BenchmarkWithPool(b *testing.B) {
var s *Small
for i := 0; i < b.N; i++ {
for j := 0; j < 10000; j++ {
s = pool.Get().(*Small)
s.a = 1
b.StopTimer()
inc(s)
b.StartTimer()
pool.Put(s)
}
}
}
다음은 풀(Pool)을 사용하지 않는 벤치마크와 이를 활용하는 벤치마크 두 가지입니다.
작성자 Go version : go version go1.16 windows/amd64
cpu: Intel(R) Core(TM) i5-8265U CPU @ 1.60GHz
BenchmarkWithoutPool-8 122 11276349 ns/op 160001 B/op 10000 allocs/op
BenchmarkWithPool-8 324 3764665 ns/op 6 B/op 0 allocs/op
블로그 원본 결과
name | time/op | alloc/op | allocs/op |
WithoutPool-8 | 3.02ms ± 1% | 160kB ± 0% | 1.05kB ± 1% |
WithPool-8 | 1.36ms ± 6% | 1.05kB ± 0% | 3.00 ± 0% |
반복문에 10,000번 실행되는 반복문이 있기 때문에 풀을 사용하지 않는 벤치마크는 힙에 10,000를 할당했지만 풀이 있는 벤치마크는 3개만 할당했습니다. 할당 3개는 풀에 의해 이루어지지만 struct 의 한 인스턴스만 할당되었습니다. sync.Pool 사용은 훨씬 빠르고 메모리를 적게 사용합니다.
그러나 실제 환경에서는 풀을 사용하는 동안 애플리케이션이 힙을 새로 많이 할당할 수 있습니다. 이 경우 메모리가 증가하면 가비지 콜렉터가 실행됩니다. 또한 명령어 runtime.GC()로 벤치마크에서 가비지 콜렉터를 강제로 사용할 수도 있습니다.
name | time/op | alloc/op | allocs/op |
WithoutPool-8 | 993ms ± 1% | 249kB ± 2% | 10.9k ± 0% |
WithPool-8 | 1.03s ± 4% | 10.6MB ± 0% | 31.0k ± 0% |
이제 풀에서 성능이 저하되고 사용된 할당 및 메모리 수가 훨씬 더 많아졌다는 것을 알 수 있습니다. 그 이유를 이해하기 위해 패키지 안을 더 자세히 살펴보도록 해야합니다.
sync/pool.go를 자세히 살펴보면 이전 문제에 대응할 수 있는 패키지의 초기화가 표시됩니다.
func init() {
runtime_registerPoolCleanup(poolCleanup)
}
풀을 정리하는 방법으로 런타임에 등록합니다. 그리고 이 동일한 방법은 전용 파일의 가비지 콜렉터에 의해 트리거됩니다.
runtime/mgc.go:
func gcStart(trigger gcTrigger) {
[...]
// clearpools before we start the GC
clearpools()
그렇기 때문에 가비지 수집기를 호출했을 때 성능이 저하되었습니다. 가비지 수집기가 실행될 때마다 풀이 지워집니다. 설명서에서도 이에 대해 경고하고 있습니다.
Any item stored in the Pool may be removed automatically at any time without notification
이제 워크플로를 생성하여 항목의 관리 방법을 알아보겠습니다.
생성하는 각 sync.Pool 에 대해 go는 각 프로세서에 연결된 내부 풀 poolLocal을 생성합니다. 이 내부 풀은 private 및 shared의 두 가지 특성으로 구성됩니다. private 속성은 소유자만 액세스할 수 있는 (푸시 및 팝업이 필요 없어 lock이 필요 없음) 반면 shared 속성은 다른 프로세서가 읽을 수 있으며 동시성에 안전해야 합니다. 실제로 이 풀은 단순한 로컬 캐시가 아니며, 애플리케이션의 모든 스레드/고루틴에서 사용할 수 있습니다.
Go 버전 1.13은 공유 항목의 액세스를 개선하고 가비지 콜렉터 및 풀 삭제와 관련된 문제를 해결할 새로운 캐시도 제공합니다.
Go 버전 1.13은 이중 연결 리스트를 lock을 제거하고 공유 액세스를 향상시키는 공유 풀로 가져옵니다. 이것은 캐시를 개선하기 위한 기초입니다. 다음은 공유 액세스의 새 워크플로우입니다.
이 새로운 체인 풀에서는 각 프로세서가 큐의 맨 앞에 푸시 및 팝이 있는 반면 공유 액세스는 테일로부터 팝업됩니다. 큐의 헤드는 next/prev 속성 덕분에 이전 구조와 연결될 새 구조를 두 배 더 크게 할당하여 확장할 수 있습니다. 초기 구조의 기본 크기는 8개입니다. 즉, 두 번째 구조에는 16개, 세 번째에서는 32개 등이 포함됩니다.
또한 이제 lock이 필요하지 않으며 코드는 원자적인 연산을 사용합니다.
새로운 캐시와 관련하여, 새로운 전략은 매우 간단합니다. 이제 활성 풀과 아카이브 풀 두 세트가 있습니다. 가비지 콜렉터가 실행될 때 각 풀의 참조를 해당 풀 내의 새 속성에 유지합니다. 그리고 현재 풀을 없애기 전에 아카이브된 풀에 풀 집합을 복사합니다.
// Drop victim caches from all pools.
for _, p := range oldPools {
p.victim = nil
p.victimSize = 0
}
// Move primary cache to victim cache.
for _, p := range allPools {
p.victim = p.local
p.victimSize = p.localSize
p.local = nil
p.localSize = 0
}
// The pools with non-empty primary caches now have non-empty
// victim caches and no pools have primary caches.
oldPools, allPools = allPools, nil
이 전략을 통해 이제 앱에는 백업을 통해 새 항목을 생성/수집하는 가비지 콜렉터의 사이클이 한 번 더 있게 됩니다. 워크플로에서 victim cache (ko.wikipedia.org/wiki/CPU_%EC%BA%90%EC%8B%9C) 는 프로세스 종료 시 공유 풀 다음에 요청됩니다.
멀티 스레드 모델과 고루틴 (0) | 2021.04.05 |
---|---|
Go 에서 싱글톤 패턴은 어떻게 작동할까(번역) (0) | 2021.03.21 |
Sort 정렬 인터페이스 구현 (0) | 2021.02.21 |