본문 바로가기
프로그래밍/Golang

[GOLANG] 공부 (1)

by 채연2 2020. 10. 29.

* 클로저를 고루틴으로 실행할 때 반복문에 의해 바뀌는 변수는 반드시 매개변수로 넘겨줌 ! > 고루틴은 반복문이 완전히 끝난 다음에 생성됨

 

* <- 채널변수 는 채널에서 값이 들어올 때까지 대기, 채널에 값이 들어오면 대기를 끝내고 다음 코드를 실행 ==> 채널은 값을 주고 받는 동시에 동기화 역할까지 수행
* 동기 채널은 보내는 쪽에서는 값을 받을 때까지 대기하고, 받는 쪽에서는 채널에 값이 들어올 때까지 대기함 > 동기 채널을 활용하면 고루틴의 코드 실행 순서 제어 가능
* 채널에 버퍼를 1개 이상 설정하면 비동기 채널이 생성됨 (asynchronous channel)
done := make(chan bool, 2) //버퍼가 2개인 비동기 채널 생성
* 비동기 채널은 보내는 쪽에서 버퍼가 가득 차면 실행을 멈추고 대기하며 받는 쪽에서는 버퍼에 값이 없으면 대기;
* 비동기 채널을 사용할 때는 실행 순서나 채널 사용 방법에 좀 더 신경써야 함
* range close 함수의 특징
- 이미 닫힌 채널에 값을 보내면 panic 발생
- 채널을 닫으면 range 루프가 종료됨  ---> 그래서 range로 값을 꺼낼 때 close를 써주는건가,,?
- 채널이 열려있고, 값이 들어오지 않는다면 range는 실행되지 않고 계속 대기. 채널에 값이 들어오면 그때부터 range가 계속 반복됨
* 채널을 가져온 뒤 두 번째 리턴값으로 채널이 닫혔는지 확인 가능
* 보내기 전용 및 받기 전용 채널은 채널 앞 뒤로 <- 연산자를 붙여서 만듦 > 보통 함수의 매개변수로 사용하거나, 구조체의 필드로 사용
- 보내기 전용(send-only) : chan<- 자료형 // c chan<- int 는 int형 보내기 전용 채널 c를 뜻함. 보내기 전용 채널에서 값을 가져오려고 하면 컴파일 에러
- 받기 전용(receive-only) : <-chan 자료형 // c <-chan int 는 int형 받기 전용 채널 c를 뜻함. range 또는 <-채널 형식으로 값을 꺼낼 수만 있으며 값을 보내려고 하면 컴파일 에러

 

* 보통 select를 계속 처리할 수 있도록 for로 반복 (반복하지 않으면 한 번만 실행되고 끝남)

* 채널에 값이 들어오지 않으면 select default가 즉시 실행되므로 적절한 처리를 하지 않으면 CPU 코어 모두 점유하므로 주의

* select cate에서는 time.After와 같이 받기 전용 채널을 리턴하는 함수 사용 가능

      - case <- time.After(50 * time.Millisecond);

* select 분기문에서는 채널에 값을 보내는 case가 있다면 항상 갑을 보냄 > 채널에 값이 들어왔을 때는 값을 받는 case가 실행됨

 

* 채널 이외에도 고루틴의 실행 흐름을 제어하는 동기화 객체
- 뮤택스(Mutex) : 상호 배제(mutual exclusion), 여러 고루틴에서 공유되는 데이터를 보호
- RWMutex : 읽기/쓰기 뮤택스, 읽기와 쓰기 동작을 나누어서 잠금(lock)을 걸 수 있음
- Cond : 조건 변수(condition variable), 대기하고 있는 하나의 객체를 깨울 수도 있고 여러 개를 동시에 깨울 수도 있음
- Once : 특정 함수를 딱 한 번만 실행할 때 사용
- Pool : 멀티 고루틴에서 사용할 수 있는 객체 풀, 자주 사용하는 객체를 풀에 보관했다가 다시 사용
- WaitGroup : 고루틴이 모두 끝날 때까지 기다리는 기능
- Atomic : 원자적 연산이라고도 하며 더 이상 쪼갤 수 없는 연산, 멀티 고루틴이나 멀티 코어 환경에서 안전하게 값을 연산하는 기능

 

* 뮤텍스 구조체와 함수
- sync.Mutex //var mutex = new(sync.Mutex)

- func(m *Mutex) Lock() //뮤택스 잠금 mutex.Lock()

- func(m *Mutex) UnLock() //뮤텍스 잠금 해제 mutex.Unlock()

* runtime.GOMAXPROCS(runtime.numCPU()) : 모든 CPU 사용

* runtime.GOMAXPROCS(1) : CPU 코어 1개만 사용

* 경쟁 조건(Race condition) : 2개 이상의 고루틴이 경합을 벌이면서 동시에 공유 데이터에 접근함으로써 함수 실행이 정확하게 처리되지 않게 되는 상황?
> CPU 코어가 한 개라도 반복 횟수가 많을 때, CPU 코어가 두 개 이상일 때 발생

* runtime.Gosched() : 다른 고루틴이 CPU를 사용할 수 있도록 양보(yield) > time.Sleep 함수보다 더 명확
* 뮤텍스는 보호를 시작할 부분에서 Lock() 사용, 보호를 끝낼 부분에서 Unlock() 사용. Lock, Unlock 함수는 반드시 짝을 맞춰야 함 > 그렇지 않으면 데드락(deadlock, 교착 상태) 발생
* 읽기/쓰기 뮤텍스 구조체와 함수

- sync.RWMutex //var rwMutex = new(sync.RWMutex)
- func (rw *RWMutex) Lock(), func (rw *RWMutex) Unlock() //쓰기 뮤텍스 잠금, 잠금 해제
- func (rw *RWMutex) RLock(), func (rw *RWMutex) RUnlock() //읽기 뮤텍스 잠금, 잠금 해제
* RWMutex는 읽기 동작이 모두 끝나야 쓰기 동작이 시작됨. 마찬가지로 쓰기 동작이 끝나야 읽기 동작이 시작됨. 읽기 동작끼리는 서로 막지 않고 항상 동시에 실행
* RWMutex는 중요한 쓰기 작업을 할 때 다른 곳에서 이전 데이터를 읽지 못하도록 방지하거나 읽기 작업을 할 때 데이터가 바뀌는 상황을 방지할 때 사용됨.
* RWMutex는 쓰기 동작보다 읽기 동작이 많을 때 유리

 

* 조건변수 함수
- sync.Cond
- func NewCond(l Locker) *Cond //var mutex = new(sync.Mutex) var cond = sync.NewCond(mutex)
- func (c *Cond) Wait() //고루틴 실행을 멈추고 대기
- func (c *Cond) Signal() //대기하고 있는 고루틴 하나만 깨움
- func (c *Cond) Broadcast() //대기하고 있는 모든 고루틴을 깨움
* 대기할 때는 고루틴 안에서 Wait() 사용하며, 대기하는 고루틴을 깨울 때는 Signal()을 사용 > Wait 함수 부분은 뮤텍스의 Lock, Unlock 함수로 보호
* 대기하고 있는 모든 고루틴을 깨울 때는 Broadcast() 사용

 

* Once 구조체와 함수
- sync.Once //once := new(sync.Once)
- func(*Once) Do(f func()) //함수를 한 번만 실행  once.Do(함수명)
* Do 함수에는 실행할 함수 이름을 지정하거나 클로저 형태로 함수를 지정 가능. 어떤 상황이든 상관없이 지정된 함수를 딱 한 번만 실행시킴.
* Once는 복잡한 반복문 안에서 각종 초기화를 할 때 유용

 

* Pool은 일종의 캐시라고 할 수 있으며 메모리 할당과 해제 횟수를 줄여 성능을 높이고자 할 때 사용. 여러 고루틴에서 동시에 사용 가능
* Pool의 구조체와 함수
- sync.Pool
- func (p *Pool) Get() interface{} //풀에 보관된 객체를 가져옴
- func (p *Pool) Put(x interface{}) //풀에 객체를 보관
* Pool 할당 방법 > Pool에 객체가 들어있다면 New 필드의 함수는 호출되지 않고, 보관된 객체가 리턴됨
pool := sync.Pool{                    // 풀 할당
New: func() interface{} {         // Get 함수를 사용했을 때 호출될 함수 정의
data := new(Data)             // 새 메모리 할당
data.tag = "new"              // 태그 설정 > Pool의 객체 사용 상황을 알아보기 위함 (옵션)
data.buffer = make([]int, 10) // 슬라이스 공간 할당
return data                   // 할당한 메모리(객체) 리턴
},
}
data := pool.Get().(*Data) //풀에서 *Data 타입으로 데이터를 가져옴 > Get 함수로 객체를 꺼낸 뒤에는 반드시 type assertion 해줘야 함.
pool.Put(data)
* Pool을 사용하면 메모리를 효율적으로 관리 가능 > 수명 주기가 짧은 객체는 부적합

 

* time..Sleep 이나 fmt.Scanln 함수를 사용하여 고루틴이 끝날 때까지 임시로 대기
* WaitGroup 구조체와 함수
- sync.WaitGroup //wg := new(sync.WaitGroup)
- func (wg *WaitGroup) Add(delta int) //대기 그룹에 고루틴 개수 추가
- func (wg *WaitGroup) Done() //고루틴이 끝났다는 것을 알려줄 때 사용
- func (wg *WaitGroup) Wait() //모든 고루틴이 끝날 때까지 기다림
* WaitGroup에서 Add()에 설정한 값과 Done() 호출되는 횟수가 같아야 함 > 그렇지 않으면 panic 발생
* defer wg.Done()와 같이 defer와 함께 사용하여 지연 호출로도 사용 가능.

 

* Atomic 연산은 여러 고루틴, CPU 코어에서 같은 변수(메모리)를 수정할 때 서로 영향을 받지 않고 안전하게 연산 가능
* "sync/atomic" 패키지에서 제공하는 원자적 연산의 종류
- Add 계열 //변수에 값을 더하고 결과를 리턴합니다.
- CompareAndSwap 계열 //변수 A와 B를 비교하여 같으면 C를 대입합니다. 그리고 A와 B가 같으면 true, 다르면 false를 리턴합니다.
- Load 계열 //변수에서 값을 가져옵니다.
- Store 계열 //변수에 값을 저장합니다.
- Swap 계열 //변수에 새 값을 대입하고, 이전 값을 리턴합니다.
* Atomic 연산에는 메모리 주소와 수정할 값을 넣음 > atomic.AddInt32(&data, 1)

 

 

320x100

'프로그래밍 > Golang' 카테고리의 다른 글

[GOLANG] 공부 (4)  (0) 2020.10.29
[GOLANG] 공부 (3)  (0) 2020.10.29
[GOLANG] 공부 (2)  (0) 2020.10.29

댓글