동시성과 고루틴, 데이터 공유에 대해서 학습하지 않았다면 아래포스팅을 다시한번 보고 오자.
https://joylucky7.tistory.com/33
◆ 일반 채널 - 동기 채널
▶ 선언
make(chan 데이터의 자료형)
▶ 채널에 데이터 저장
→ 채널 <- 데이터
▶ 채널을 통해서 값 전달 받기
→ 변수:= <- 채널
▶ 채널을 통해 데이터를 받거나 보내면 해당 고루틴은 송수신이 완료될 때까지 대기
▶ 데이터를 수신할 다른 고루틴이 없을 경우 고루틴은 대기하고 반대로 데이터를 받아야 하는데 보내는 고루틴이 없다면 기다려야 함
▶ 이 방식은 데이터를 동기화해 최신 상태로 유지할 수 있으며 다른 프로그래밍 언어에서 사용하는 잠금 방식보다 단순
package main
import (
"fmt"
"time"
)
func runLoopSend(n int, ch chan int) {
for i := 0; i < n; i++ {
ch <- i
}
close(ch)
}
func runLoopReceive(ch chan int) {
for {
i, ok := <-ch
if !ok {
break
}
fmt.Println("Received value:", i)
}
}
func main() {
myChannel := make(chan int)
go runLoopSend(10, myChannel)
go runLoopReceive(myChannel)
time.Sleep(2 * time.Second)
}
▶ 수정
func runLoopReceive(ch chan int) {
for i := range ch{
fmt.Println("Received value:", i)
}
}
◆ 버퍼 채널 - 비동기 채널
▶ 버퍼(buffered) 채널은 여러 값을 버퍼에 저장하는 특수한 채널
▶ 일반 채널과 다르게 버퍼 채널은 다음의 경우에만 대기
● 버퍼가 비어있는 채널이 데이터를 기다리는 경우
● 버퍼에 남은 공간이 없는 채널에 데이터를 보내는 경우
▶ 선언
make(chan 자료형, 개수)
package main
import (
"fmt"
"runtime"
)
func main() {
runtime.GOMAXPROCS(1)
done := make(chan bool, 2) // 버퍼가 2개인 버퍼 채널 생성
count := 10 // 반복할 횟수
go func() {
for i := 0; i < count; i++ {
done <- true // 채널에 true를 보냄
fmt.Println("고루틴 : ", i) // 반복문의 변수 출력
}
}()
for i := 0; i < count; i++ {
<-done // 버퍼에 값이 없으면 대기, 값을 꺼냄
fmt.Println("메인 함수 : ", i) // 반복문의 변수 출력
}
}
◆ Select
▶ select 구문을 사용해 여러 채널을 동시에 제어할 수 있음
▶ select를 사용해서 여러 채널에 데이터를 보내거나 받을 수 있고 먼저 활성화된 채널의 코드를 실행할 수 있음
select {
case i := <-ch:
fmt.Println("Received value:", i)
case <-time.After(1 * time.Second):
fmt.Printin("timed out") }
● select 구문은 2개의 채널을 사용
● 첫 번째 채널은 데이터를 수신하는 ch 채널이고 두 번째 채널은 time.After() 함수인데 이 함수는 select 구문에서 많이 사용되는 함수로 설정한 시간이 지나면 데이터가 도착하며 이 시간 동안 채널을 대기 시킴
● 채널에서 데이터를 송수신 시 타임아웃(timeout)을 설정할 때 주로 time.After() 함수를 사용
package main
import (
"fmt"
"time"
)
func main() {
c1 := make(chan int) // int형 채널 생성
c2 := make(chan string) // string 채널 생성
go func() {
for {
c1 <- 10 // 채널 c1에 10을 보낸 뒤
time.Sleep(100 * time.Millisecond) // 100 밀리초 대기
}
}()
go func() {
for {
c2 <- "두번째 채널" // 채널 c2에 Hello, world!를 보낸 뒤
time.Sleep(500 * time.Millisecond) // 500 밀리초 대기
}
}()
go func() {
for {
select {
case i := <-c1: // 채널 c1에 값이 들어왔다면 값을 꺼내서 i에 대입
fmt.Println("c1 :", i) // i 값을 출력
case s := <-c2: // 채널 c2에 값이 들어왔다면 값을 꺼내서 s에 대입
fmt.Println("c2 :", s) // s 값을 출력
}
}
}()
time.Sleep(10 * time.Second) // 10초 동안 프로그램 실행
}
◆ 동기화 객체
▶ 반드시 잠금이 필요하다면 sync 패키지를 사용
▶ 고루틴 간의 소통 방법은 채널이 적합하지만 잠금이나 뮤텍스(mutex, mutual exclusion object)를 사용해야 하는 경우가 있는데 예를 들어 Go 언어의 표준 패키지인 http 패키지에서 http 서버 객체의 리스너를 제어하는 데 뮤텍스를 사용하는데 리스너는 여러 고루틴에서 접근할 수 있으므로 뮤텍스가 적절
▶ 컴퓨터 프로그래밍에서 뮤텍스는 자원(예를 들어 공유 메모리)에 여러 스레드의 접근을 제어하는 객체를 의미하는데 뮤텍스는 한 번에 한 개의 스레드만 데이터에 접근을 허용하기 때문에 붙여진 이름
▶ 소프트웨어에서 뮤텍스의 워크플로우는 일반적으로 다음과 같음
● 특정 스레드가 뮤텍스를 소유
● 다른 스레드는 뮤텍스를 소유할 수 없음
● 뮤텍스를 소유한 스레드는 마음대로 자원에 접근할 수 있음
● 작업이 끝나면 뮤텍스를 해제하고 다른 스레드는 뮤텍스를 소유하고자 서로 경쟁
▶ 고루틴은 완전한 스레드가 아니므로 뮤텍스를 사용해 여러 고루틴의 동일한 자원에 접근을 제어할 수 있음
▶ 동기화 객체 종류
● Mutex: 여러 스레드(고루틴)에서 공유되는 데이터를 보호할 때 주로 사용
● RWMutex: 읽기/쓰기 뮤텍스로 읽기와 쓰기 동작을 나누어서 잠금(락)을 걸 수 있음
● Cond: 조건 변수(condition variable)로 대기하고 있는 하나의 객체를 깨울 수도 있고 여러 개를 동시에 깨울 수도 있음
● Once: 특정 함수를 딱 한 번만 실행할 때 사용
● Pool: 멀티 스레드(고루틴)에서 사용할 수 있는 객체 풀로 자주 사용하는 객체를 풀에 보관했다가 다시 사용
● WaitGroup: 고루틴이 모두 끝날 때까지 기다리는 기능
● Atomic: 원자적 연산이라고도 하며 더 이상 쪼갤 수 없는 연산이라는 의미로 멀티 스레드(고루틴), 멀티코어 환경에서 안전하게 값을 연산하는 기능
▶ 뮤텍스 구조체
● sync.Mutex
● func (m *Mutex) Lock(): 뮤텍스 잠금
● func (m *Mutex) UnlockO: 뮤텍스 잠금 해제
▶ 뮤텍스를 사용하지 않은 경우
package main
import (
"fmt"
"runtime"
"time"
)
func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) // 모든 CPU 사용
var data = []int{} // int형 슬라이스 생성
go func() { // 고루틴에서
for i := 0; i < 1000; i++ { // 1000번 반복하면서
data = append(data, 1) // data 슬라이스에 1을 추가
runtime.Gosched() // 다른 고루틴이 CPU를 사용할 수 있도록 양보
}
}()
go func() { // 고루틴에서
for i := 0; i < 1000; i++ { // 1000번 반복하면서
data = append(data, 1) // data 슬라이스에 1을 추가
runtime.Gosched() // 다른 고루틴이 CPU를 사용할 수 있도록 양보
}
}()
time.Sleep(2 * time.Second) // 2초 대기
fmt.Println(len(data)) // data 슬라이스의 길이 출력
}
▶ 뮤텍스를 사용할때
package main
import (
"fmt"
"runtime"
"sync"
"time"
)
func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) // 모든 CPU 사용
var data = []int{}
var mutex = new(sync.Mutex)
go func() { // 고루틴에서
for i := 0; i < 1000; i++ { // 1000번 반복하면서
mutex.Lock() // 뮤텍스 잠금, data 슬라이스 보호 시작
data = append(data, 1) // data 슬라이스에 1을 추가
mutex.Unlock() // 뮤텍스 잠금 해제, data 슬라이스 보호 종료
runtime.Gosched() // 다른 고루틴이 CPU를 사용할 수 있도록 양보
}
}()
go func() { // 고루틴에서
for i := 0; i < 1000; i++ { // 1000번 반복하면서
mutex.Lock() // 뮤텍스 잠금, data 슬라이스 보호 시작
data = append(data, 1) // data 슬라이스에 1을 추가
mutex.Unlock() // 뮤텍스 잠금 해제, data 슬라이스 보호 종료
runtime.Gosched() // 다른 고루틴이 CPU를 사용할 수 있도록 양보
}
}()
time.Sleep(2 * time.Second) // 2초 대기
fmt.Println(len(data)) // data 슬라이스의 길이 출력
}
▶ 읽기 쓰기 뮤텍스
● sync.RWMutex
● func (rw *RWMutex) Lock(), func (rw *RWMutex) Unlock(): 쓰기 뮤텍스 잠금, 잠금 해제
● func (rw *RWMutex) RLock(), func (rw *RWMutex) RUnlock(): 읽기 뮤텍스 잠금 및 잠금 해제
▶ 읽기 쓰기 뮤텍스 사용하지 않는 경우
package main
import (
"fmt"
"runtime"
"time"
)
func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) // 모든 CPU 사용
var data int = 0
go func() { // 값을 쓰는 고루틴
for i := 0; i < 3; i++ {
data += 1 // data에 값 쓰기
fmt.Println("write : ", data) // data 값을 출력
time.Sleep(10 * time.Millisecond) // 10 밀리초 대기
}
}()
go func() { // 값을 읽는 고루틴
for i := 0; i < 3; i++ {
fmt.Println("read 1 : ", data) // data 값을 출력(읽기)
time.Sleep(1 * time.Second) // 1초 대기
}
}()
go func() { // 값을 읽는 고루틴
for i := 0; i < 3; i++ {
fmt.Println("read 2 : ", data) // data 값을 출력(읽기)
time.Sleep(2 * time.Second) // 2초 대기
}
}()
time.Sleep(10 * time.Second) // 10초 동안 프로그램 실행
}
▶ 읽기 쓰기 뮤텍스 사용하는 경우
package main
import (
"fmt"
"runtime"
"sync"
"time"
)
func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) // 모든 CPU 사용
var data int = 0
var rwMutex = new(sync.RWMutex) // 읽기, 쓰기 뮤텍스 생성
go func() { // 값을 쓰는 고루틴
for i := 0; i < 3; i++ {
rwMutex.Lock() // 쓰기 뮤텍스 잠금, 쓰기 보호 시작
data += 1 // data에 값 쓰기
fmt.Println("write : ", data) // data 값을 출력
time.Sleep(10 * time.Millisecond) // 10 밀리초 대기
rwMutex.Unlock() // 쓰기 뮤텍스 잠금 해제, 쓰기 보호 종료
}
}()
다음장에서는 조건변수 구조체로 시작해서 계속해서 알아보는 시간을 가져보자
https://joylucky7.tistory.com/35
'IT 초보코딩의 세계 > Go 언어' 카테고리의 다른 글
Go언어의 WaitGroup 와 원자적 연산법 4장 (12) | 2023.04.05 |
---|---|
Go언어의 조건변수, Once 사용법, Pool 구조체 3장 (0) | 2023.04.05 |
Go언어의 동시성 과 Goroutine, 데이터공유 1장 (0) | 2023.04.04 |
Go언어의 구조체(포인터, Struct) 1장 (6) | 2023.03.30 |
Go언어의 자료구조 맵(Map)와 컨테이너 (0) | 2023.03.30 |
댓글