[GO] #3-11. 패키지 탐방 (sync)

Study·2021년 6월 25일
0

고랭

목록 보기
18/18
post-thumbnail

sync

상호 배제 잠금과 같은 기본적인 동기화 기본 요소를 제공해준다.
Once 및 WaitGroup 유형을 제외하고 대부분이 저수준 라이브러리 루틴에서 사용하기 위한 것이다.
높은 수준의 동기화는 채널과 통신을 더 잘 수행될 수 있다.

타입

Cond

type Cont struct {
    // 조건을 관찰하거나 변경하는 동안 L 을 유지한다.
    L Locker
    // 내보내지지 않은 필드 포함
}

Cond 는 이벤트 발생을 기다리거나 알리는 고루틴에 대한 특정 시점의 조건 변수를 구현한다.

조건을 변경하고 Wait 메소드를 호출할 때 보유해야 한다.

fuc NewCond(l Locker) *Cond

새로운 Cond 를 반환한다.

func (c *Cond) Broadcast()

c 에서 대기 중인 모든 고루틴을 깨운다.

func (c *Cond) Signal()

c 를 기다리는 하나의 고루틴을 깨운다.

BroadcastSignal 은 발신자가 호출 중에 c.L 을 보류하는 것은 허용되지만 필수는 아니다.

func (c *Cond) Wait()

c.L 을 원자적으로 잠금 해제하고 호출 중인 고루틴의 실행을 일시 중단한다.
나중에 실행을 재개 한 후 Wait 는 리턴하기 전에 c.L 을 잠근다.

다른 시스템과 달리 Wait 는 Broadcast 또는 Signal 에 의해 깨어나지 않으면 돌아올 수 없다.

Wait 가 처음 재개될 때 c.L 이 잠기지 않기 때문에 일반적으로 호출자는 Wait 가 반환될 때 조건이 참이라고 가정할 수 없다.
대신 호출자는 루프 안에 Wait 이 있어야 한다.

예제

c.L.Lock()
for !condition() {
    c.Wait()
}
... 상태값 제어 ...
c.L.Unlock()

Locker

Locker 는 잠금을 해제하거나 잠글 수 있는 개체이다.

type Locker interface {
    Lock()
    Unlock()
}

Map

type Map struct {
    // 필터링되거나 내보내지지 않은 필드 포함
}

Map 은 고의 map[interface{}]interface{} 와 비슷하지만 주가적인 잠금이나 조정 없이 멀티 고루틴에 의한 동시 사용에서 안전하다.

Map 타입은 전문화되어 있다고 볼 수 있다.
대부분의 코드는 타입 안전성을 높이고 맵 콘텐츠와 함께 다른 불변성을 더 쉽게 유지하기 위해 별도의 잠금 또는 조정을 함께 일반 GO 맵을 대신 사용해야 한다.

Map 타입은 두 가지 일반적인 사용 사례에 최적화되어 있다.

  1. 주어진 키에 대하 항목이 한 번만 기록되었지만 증가하는 캐시에서와 같이 여러번 읽히는 경우
  2. 여러 고루틴이 읽고, 분리된 키 집합에 대한 항목을 덮어 쓴다.

이 두 가지 경우에 맵을 사용하면 별도의 Mutex 또는 RWMutex 와 쌍을 이루는 GO 맵에 비해 잠금 경합을 크게 줄일 수 있다.

func (m *Map) Delete(key interface{})

키에 대한 값을 삭제한다.

func (m *Map) Load(key interface{}) (value interface{}, ok bool)

키에 대한 저장된 값을 반환하거나 값이 없을 경우 nil 을 반환한다.
ok 결과가 맵에서 값이 발견되었는지 여부를 반환한다.

func (m *Map) LoadAndDelete(key interface{}) 
	(value interface{}, loaded bool)

키 값을 삭제하고 이전 값이 있는 경우 반환한다.

func (m *Map) LoadOrStore(key, value interface{}) 
	(actual interface{}, loaded bool)
    

값이 있을 경우 기존 값을 반환한다. 그렇지 않으면 주어진 값을 저장하고 반환한다.

LoadAndDeleteLoadOrStore 의 로드된 결과는 키가 있는지 여부를 반환한다.

func (m *Map) Range(f func(key, value interface{}) bool)

맵에 있는 각 키와 값에 대해 f 를 순차적으로 호출한다.
f 가 false 를 반환하면 범위는 반복을 멈춘다.

맵 콘텐츠의 일관된 스냅 샷과 반드시 일치하지는 않으며 키는 두 번 이상 방문되지 않지만, 키 값이 동시에 저장되거나 삭제되면 Range 는 어느 지점에서나 해당 키에 대한 매핑을 반영할 수 있다.

Range 는 일정한 수의 호출 후 ffalse 를 반환하더라도 맵의 요소 수와 함께 시간 복잡도가 O(N) 일 수 있다.

func (m *Map) Store(key, value interface{})

키 값을 설정한다.

Mutex

type Mutex struct {
    // 필터링되거나 내보내지지 않은 필드 포함 
}

뮤텍스는 상호 배제 잠금이다. 제로값은 잠금되지 않은 뮤텍스이다.

뮤텍스는 첫 번째 사용 후에 복사해서는 안된다.

func (m *Mutex) Lock()

m 을 잠근다.

만약 잠금을 이미 사용했다면, 뮤텍스가 사용 가능할 때까지 고루틴은 차단된다.

func (m *Mutex) Unlock()

m 잠금을 푼다.

잠금 해제에 들어갈 때 m 이 잠기지 않으면 런타임 오류이다.

잠긴 뮤텍스는 특정 고루틴과 연관되지 않으며, 하나의 고루틴이 뮤텍스를 잠그고 다른 고루틴이 잠금을 해제하도록 할 수 있다.

Once

type Once struct {
    // 필터링되거나 내보내지지 않은 필드 포함 
}

정확히 하나의 작업을 수행하는데 사용하는 개체이다.

처음 사용한 후에 복사해서는 안된다.

func (o *Once) Do(f func())

Once 는 해당 Once 의 인스턴스에 대해 처음 DO 를 호출하는 경우에만 함수를 호출한다.

즉, 주어진 var once Once 에서 once.Do(f) 가 여러 번 호출되면 f 가 각 호출에서 다른 값을 가지더라도 첫 번째 호출만 f 를 호출한다.

각 함수를 실행하려면 새로운 Once 인스턴스가 필요하다.

Do 는 정확히 한 번 실행해야하는 초기화를 위한 것이다.
f 는 무조건적이니 config.once.Do(func() {config.init(filename) }) 에 의해 호출된 함수에 대한 인수를 잡기 위해 함수 리터럴을 사용해야 할 수 있다.

왜냐면 f 에 대한 한 번의 호출이 반환될 때까지 Do 에 대한 호출이 반환되지 않아, f 로 인해 Do 가 호출되면 교착 상태가 된다.

f 가 패닉이면, Do 는 그것이 돌아온 것으로 간주한다. Do 의 향후 호출은 f 를 호출하지 않고 반환된다.

Pool

type Pool struct {
    // New 는 Get 이 그렇지 않으면 0을 반환할 때
    // 값을 생성하는 함수를 지정한다.
    // Get 호출과 동시에 변경할 수 없다.
    New func() interface{}
    // 필터링되거나 내보내지지 않은 필드 포함
}

Pool 은 개별적으로 저장하고 검색할 수 있는 임시 개체의 집합이다.

풀에서 저장된 모든 항목은 통지없이 언제든지 자동으로 제거될 수 있다.
이 경우 풀이 유일한 참조를 보유하면 항목이 할당 해제될 수 있다.

풀은 여러 고루틴에서 동시에 사용하기에 안전하다.

풀의 목적은 나중에 재사용할 수 있도록 할당되었지만 사용되지 않은 항목을 캐시하여 가비지에 대한 부담을 줄이는 것이다.
즉, 효율적이고 스레드로부터 안전한 자유 목록을 쉽게 만들 수 있다. 그러나 모든 자유 리스트엔 적합하지 않다.

풀의 적절한 사용은 패키지의 동시 독립 클라이언트 간에 자동으로 공유되고 잠재적으로 재사용되는 임시 항목 그룹을 관리하는 것이다.
풀은 많은 클라이언트에서 할당 오버 헤드를 생각하는 방법을 제공한다.

풀 사용의 좋은 예는 임시 출력 버퍼의 동적 크기 저장소를 유지하는 fmt 패키지에 있다. StoreLoad에 따라 확장되고 정지할 때 축소된다.

반면 수명이 짧은 개체의 일부로 유지 관리되는 자유 리스트는 해당 시나리오에서 오버 헤드가 잘 상각되지 않기 때문에 풀에 적합하지 않다.
그러한 객체가 자신의 자유 목록을 구현하도록하는 것이 더 효율적이다.

func (p *Pool) Get() interface{}

풀에서 임의의 항목을 선택하고 풀에서 제거하고 호출자에게 반환한다.
Get 은 풀을 무시하고 비어있는 것으로 처리하도록 선택할 수 있다.
호출자는 Put 에 전달된 값과 Get 에서 반환된 값 사이에 어떤 관계도 가정해서는 안된다.

그렇지 않으면 Getnil 을 반환하고 p.Newnil 이 아니면 p.New 를 호출한 결과를 반환한다.

func (p *Pool) Put(x interface{})

풀에 x 를 추가한다.

RWMutex

type RWMutex struct {
    // 필터링되거나 내보내지지 않은 필드 포함
}

읽기, 쓰기 상호 배제 잠금이다.
잠금은 임의의 수의 판독기 또는 단일 작성자가 보유할 수 있다.
RWMutex 의 0 값은 잠금 해제된 뮤텍스이다.

RWMutex 는 처음 사용한 후 복사해서는 안된다.

고루틴이 읽기를 위해 RWMutex 를 보유하고 다른 고루틴이 잠금을 호출할 수 있는 경우, 어떤 고루틴도 초기 읽기 잠금이 해제될 때까지 읽기 잠금을 획득할 수 있을 것으로 기대해서는 안된다. 특히 이것은 재귀 읽기 잠금을 금지한다. 이는 결국 잠금을 사용할 수 있게 하기 위한 것이다.
차단된 잠금 호출은 새 독자가 잠금을 획득하지 못하도록 제외한다.

func (rw *RWMutex) Lock()

쓰기를 위해 rw 를 잠근다.
읽기 또는 쓰기를 위해 Lock 이 이미 잠겨있는 경우 잠금을 사용할 수 있을 때까지 잠금이 차단된다.

func (rw *RWMutex) RLock()

읽기 위해 rw 를 잠근다.

재귀적 읽기 잠금에 사용해서는 안된다.
차단된 잠금 호출은 새 리더가 잠금을 획득하지 못하도록 제외한다.

func (rw *RWMutex) RLocker()

rw.RLockrw.RUnlock 을 호출하여 LockUnlock 메소드를 구현하는 Locker 인터페이스를 반환한다.

func (rw *RWMutex) RUnlock()

단일 RLock 호출을 취소한다.
다른 동시 독자에게는 영향을 미치지 않는다.
rwRUnlock 항목을 읽을 때 잠기지 않은 경우 런타임 오류이다.

func (rw *RWMutex) Unlock()

쓰기를 위해 rw 를 잠금 해제한다.
잠금 해제 항목에 쓰기 위해 rw 가 잠겨있지 않으면 런타임 오류이다.

뮤텍스와 마찬가지로 잠긴 RWMutex 는 특정 고루틴과 연관되지 않는다.
한 고루틴은 RWMutexRLock 한 다음 다른 고루틴을 RUnlock 하도록 할 수 있다.

WaitGroup

type WaitGroup struct {
    // 필터링되거나 내보내지지 않은 필드 포함
}

WaitGroup 은 고루틴 컬렉션이 완료되기를 기다린다. 메인 고루틴은 대기 할 고루틴의 수를 설정하기 위해 Add를 호출한다.
그런 다음 각 고루틴이 실행되고 완료되면 Done을 호출한다. 동시에 모든 고루틴이 완료 될 때까지 대기를 사용하여 차단할 수 있다.

WaitGroup 는 첫 번째 사용 후 복사해서는 안된다.

func (wg *WaitGroup) Add(delta int)

AddWaitGroup 카운터에 음수일 수있는 델타를 추가한다.
카운터가 0이되면 Wait 에서 차단된 모든 고루틴이 해제된다.
카운터가 음수이면 패닉을 추가한다.

카운터가 0 일 때 발생하는 양의 델타가있는 호출은 대기 전에 발생해야한다.
음수 델타가 있는 호출 또는 카운터가 0보다 클 때 시작되는 양수 델타가있는 호출은 언제든지 발생할 수 있다.

일반적으로 이는 대기 할 고루틴 또는 기타 이벤트를 생성기 전에 Add에 대한 호출이 실행되어야 함을 의미한다.
WaitGroup이 여러 독립된 이벤트 집합을 기다리기 위해 재사용되는 경우 모든 이전 Wait 호출이 반환 된 후에 새로운 Add 호출이 발생해야한다.

func (wg *WaitGroup) Done()

WaitGroup 카운터를 1씩 감소시킨다.

func (wg *WaitGroup) Wait()

카운터가 0이 될 때까지 대기한다.

sync/atomic

동기화 알고리즘 구현에 유용한 저수준 원자 메모리 기본 요소를 제공한다.

이런 기능을 올바르게 사용하려면 많은 주의가 필요하다.
특수한 저수준 응용 프로그램을 제외하고 동기화는 채널 또는 동기화 패키지의 기능을 사용하여 더 잘 수행된다.
의사소통을 통해 메모리를 공유하자. 메모리를 공유하여 의사소통 하지 말자.

SwapT 함수에 의해 구현되는 스왑 작업은 다음과 같은 원자 단위이다.

old = *addr
*addr = new
return old

CompareAndSwapT 함수에 의해 구현되는 비교 및 교체 작업은 다음과 같은 원자 단위다.

if *addr == old {
    *addr = new
    return true
}
return false

AddT 함수에 의해 구현된 추가 작업은 다음과 같은 원자 단위이다.

*addr += delta
return *addr

LoadTStoreT 함수에 의해 구현된 로드 및 저장 작업은 return *addr*addr = val 과 같은 원자 단위다.

함수

func AddInt32(addr *int32, delta int32) (new int32)
func AddInt64(addr *int64, delta int64) (new int64)
func AddUInt32(addr *uint32, delta uint32) (new uint32)
func AddUInt64(addr *uint64, delta uint64) (new uint64)
func AddUIntptr(addr *uintptr, delta uintptr) (new uintptr)

AddInt32, AddInt64, AddUInt32, AddUInt64, AddUIntptr*addr 에 델타를 원자적으로 추가하고 새 값을 반환한다.

func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)
func CompareAndSwapInt64(adadr *int64, old, new int64) (swapped bool)
func CompareAndSwapUInt32(adadr *uint32, old, new int32) (swapped bool)
func CompareAndSwapUInt64(adadr *uint64, old, new int64) (swapped bool)
func CompareAndSwapUintptr(addr *uintptr, old, new uintptr) (swapped bool)

CompareAndSwapInt32, CompareAndSwapInt64, CompareAndSwapUint32, CompareAndSwapUint64 는 [int32, int64, uint32, uint64, intptr] 값에 대해 비교 및 교체 작업을 실행한다.

func CompareAndSwapPointer(addr *unsafe.Pointer, old, new unsafe.Pointer) (swapped bool)

안전하지 않은 포인터 값에 대해 비교 및 교체 작업을 실행한다.

func LoadInt32(addr *int32) (val bool)
func LoadInt64(adadr *int64) (val bool)
func LoadUInt64(adadr *uint32) (val bool)
func LoadUInt64(adadr *uint64) (val bool)
func LoadUintptr(addr *uintptr) (val bool)
func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)

각각 *addr 을 원자적으로 로드한다.

func StoreInt32(addr *int32, val int32)
func StoreInt64(addr *int64, val int64)
func StoreUInt32(addr *uint32, val uint32)
func StoreUInt64(addr *uint64, val int64)
func StoreUintptr(addr *uintptr, val uintptr)
func StorePointer(addr *unsafe.Pointer, val usafe.Pointer)

각각 *addr 에 val 을 원자적으로 저장한다.

func SwapInt32(addr *int32, new int32) (old int32)
func SwapInt64(adadr *int64, new int64) (old int64)
func SwapUInt32(addr *uint32, new uint32) (old uint32)
func SwapUInt64(addr *uint64, new int64) (old uint64)
func SwapUintptr(addr *uintptr, new uintptr) (old uintptr)
func SwapPointer(addr *unsafe.Pointer, new usafe.Pointer) (old unsafe.Pointer)

각각 addr 에 새 항목을 원자적으로 저장하고 이전 addr 값을 반환한다.

타입

Value

type Value struct {
    // 필터링되거나 내보내지지 않은 필드 포함
}

일관된 타입 값의 원자로드 및 저장을 제공한다.
Value 의 제로값은 Load 에서 nil 을 반환한다.
Store 가 호출되면 값을 복사해서는 안된다.

사용한 후에 복사를 할 수 없다.

func (v *Value) Load() (x interface{})

Load 는 가장 최근 Store 에서 설정한 값을 반환한다. 이 값에 대해 Store 호출이 없으면 nil 을 반환한다.

func (v *Value) Store(x interface{})

StoreValue 의 값을 x 로 설정한다.
지정된 Value 에 대한 모든 Store 호출은 동일한 구체적인 유형의 값을 사용해야 한다.
일관되지 않은 유형의 저장은 Store(nil) 처럼 패닉된다.

profile
Study

0개의 댓글