채널
- 고루틴끼리 데이터를 송수신하기 위한 통로 역할
- 채널을 정의한 타입으로만 송수신 가능
- FIFO방식으로 데이터를 전달
- 먼저 송신된 데이터먼저 받는다
- 데이터 송수신 시 송신측과 수신측은 송수신이 완료될 때까지 대기한다.
- deadlock발생 가능
func TestGoroutine(t *testing.T) {
wg := &sync.WaitGroup{}
wg.Add(2)
myChan := make(chan int)
go func() {
defer wg.Done()
myChan <- 100
value1 := <-myChan
fmt.Println(value1)
}()
go func() {
defer wg.Done()
value2 := <-myChan
myChan <- value2 + 100
fmt.Println(value2)
}()
wg.Wait()
}
myChan이라는 int타입의 채널을 생성해서 고루틴끼리 주고받는다.
- 채널생성
channel:=make(chan int)
- 채널을 통해 데이터 송신
myChan <- 100
- 채널을 통해 데이터를 수신
value2 := <-myChan
deadlock
- 정의: 서로다른 두 프로세스가 자신의 자원을 점유하고있는 상태에서 상대방이 점유중인 자원을 요청하는 상태
- deadlock은 4가지 조건을 모두 만족해야 발생한다.
- Mutual Exclusion: 임의의 순간에 프로세스는 반드시 자원을 점유중이어야 한다.
- Hold and Wait: 자원을 점유중인 상태에서 다른 프로세스의 자원요청을 대기한다.
- No Preemption: 프로세스가 직접 자원을 반납하기 전까지 다른 프로세스로가 점유중인 자원을 뺏을 수 없다.
- Circular Wait: 자원을 점유하고 다른 프로세스의 자원을 요청하는 상태가 마치 Circular처럼 이루어져야 한다.
Channel의 Deadlock
- case1: 채널로 데이터를 송신한 상태에서 프로세스가 종료
func TestGoroutine(t *testing.T) {
wg := &sync.WaitGroup{}
wg.Add(1)
myChan := make(chan int)
go func() {
defer wg.Done()
myChan <- 100 // deadlock!!
}()
wg.Wait()
}
- case2: 채널을 통해 데이터 수신을 영원히 대기
func TestGoroutine(t *testing.T) {
wg := &sync.WaitGroup{}
wg.Add(1)
myChan := make(chan int)
go func() {
defer wg.Done()
value := <-myChan // deadlock!!
fmt.Println(value)
}()
wg.Wait()
}
위의 코드를 돌려보면 프로그램이 무한히 대기하는 현상이 발생하지 않고 deadlock이 발생하였다는 에러를 반환한다. 즉 golang은 deadlock detection기능을 지원한다는 뜻이다.
golang의 deadlock detection은 go로 만든 프로세스 내부에서만 탐지되며 OS상에서 발생하는 deadlock은 탐지할 수 없다.
채널의 버퍼
- 채널도 버퍼가 존재하여 송신중인 값을 잠시 저장할 수 있다.
- 왜 필요한가? => deadlock방지
- 수신측이 받을때까지 송신측이 무한정 대기하는 상태를 방지
- default: 0
- 버퍼가 100인 채널 생성 === 송신할 데이터 100개를 버퍼에 넣어놓고 다른일을 할 수 있다.
channel:=make(chan int,100)
- 버퍼를 놓는다고 해서 deadlock에 완전히 벗어나지 않는다.
- 수신측에서 발생하는 deadlock은 해결하지 못한다
- 100개의 데이터를 버퍼에 저장된 상태에서 101번째의 데이터를 송신할 때는 송신측에서 버퍼에 삽입되기 전까지 대기한다.
select
- 고루틴의 switch기능
- 왜 필요한가?
- 여러개의 채널을 사용할 때 먼저 응답받는 채널을 먼저 처리가 가능하다
func TestGoroutine(t *testing.T) {
wg := &sync.WaitGroup{}
wg.Add(1)
chan1 := make(chan int)
chan2 := make(chan int)
chan3 := make(chan int)
go func() {
defer wg.Done()
select {
case retChan1 := <-chan1:
fmt.Println(retChan1)
case retChan2 := <-chan2:
fmt.Println(retChan2)
case retChan3 := <-chan3:
fmt.Println(retChan3)
}
}()
chan2 <- 100
wg.Wait()
}
채널들을 case로 지정하고 응답이 오는 채널을 처리해준다. 채널에서 값이 올 때까지 select에서 block된다. block이 아닌 처리과정이 더 필요하다면 default를 사용하면 된다.
func TestGoroutine(t *testing.T) {
wg := &sync.WaitGroup{}
wg.Add(1)
chan1 := make(chan int)
chan2 := make(chan int)
chan3 := make(chan int)
go func() {
defer wg.Done()
select {
case retChan1 := <-chan1:
fmt.Println(retChan1)
case retChan2 := <-chan2:
fmt.Println(retChan2)
case retChan3 := <-chan3:
fmt.Println(retChan3)
default:
fmt.Println("not found")
}
}()
wg.Wait()
}
채널들이 모두 값을 수신하기위해 대기중인 상태이므로 default를 실행한다.
context
- 여러개의 고루틴을 제어할 수 있는 golang의 내장객체
- context객체들은 트리구조로 구성된다.
- root: background
작업 취소 context
- 개발자가 원하는 타이밍에 고루틴을 제어시키는 기능
ctx, cancelFunc:= context.WithCancel(parentContext)
- ctx: 작업 취소 context
- cancelFunc: 작업 취소 명령을 요청하는 함수
- parentContext: 부모 context
func TestGoroutine(t *testing.T) {
wg := &sync.WaitGroup{}
wg.Add(1)
ctx, cancelFunc := context.WithCancel(context.Background())
go func() {
defer wg.Done()
tick := time.Tick(1 * time.Second)
// 작업취소를 수행하기 전까지 1초마다 tick...을 출력
for {
select {
// 작업 취소 명령이 떨어지면 함수를 종료시킴
case <-ctx.Done():
return
case <-tick:
fmt.Println("tick...")
}
}
}()
// 5초후 작업취소 명령을 요청
time.Sleep(5 * time.Second)
cancelFunc()
wg.Wait()
}
제한시간을 거는 context
- context를 통해 고루틴의 시간제한을 설정
- 제한시간 내로 작업을 완료하지 못하면 고루틴에 개입이 가능
ctx, cancelFunc:= context.WithTimeout(parentContext,limit)
- ctx: 작업 취소 context
- cancelFunc: 작업 취소 명령을 요청하는 함수
- parentContext: 부모 context
- limit: 유효시간
func TestGoroutine(t *testing.T) {
wg := &sync.WaitGroup{}
wg.Add(1)
// 작업시간이 최대 5초인 context생성
ctx, _ := context.WithTimeout(context.Background(), time.Second*5)
go func() {
defer wg.Done()
tick := time.Tick(1 * time.Second)
for {
// 5초동안 tick을 출력하다가 종료
select {
case <-ctx.Done():
return
case <-tick:
fmt.Println("tick...")
}
}
}()
wg.Wait()
}
작업을 지정하는 context
- context를 통해 특정 값을 전달
- key-value형식으로 context내에 값을 삽입하여 이를 불러오는 방식으로 사용
ctx:= context.WithValue(parentContext,key,value)
- ctx: 작업 취소 context
- parentContext: 부모 context
- key: context에 저장된 값을 불러오기위한 key
- type: string
- value: context내에 저장된 값
- type: {}interface -> 모든 타입이 가능함
func TestGoroutine(t *testing.T) {
wg := &sync.WaitGroup{}
wg.Add(1)
ctx := context.WithValue(context.Background(), "myFunc", func() {
fmt.Println("wahaha")
})
go func() {
defer wg.Done()
// myFunc값을 불러와서 타입assertion을 수행
myFunc := ctx.Value("myFunc").(func())
myFunc()
}()
wg.Wait()
}
context합치기
위의 context내용들을 보면 context를 생성하기 위해선 부모 context정보가 반드시 필요함을 알 수 있다. 이를 통해 여러개의 context를 묶어서 제어가 가능하다.
func TestGoroutine(t *testing.T) {
wg := &sync.WaitGroup{}
wg.Add(1)
// myFunc값을 삽입
parentCTX := context.WithValue(context.Background(), "myFunc", func() {
fmt.Println("wahaha")
})
// 제한시간을 5초로 설정
ctx, _ := context.WithTimeout(parentCTX, 5*time.Second)
go func() {
defer wg.Done()
tick := time.Tick(1 * time.Second)
for {
select {
case <-ctx.Done():
return
case <-tick:
// 값을 가져와서 함수를 실행함
myFunc := ctx.Value("myFunc").(func())
myFunc()
}
}
}()
wg.Wait()
}
제한시간이 5초동안 myFunc값을 불러와서 호출시키는 context
'golang' 카테고리의 다른 글
golang1.18릴리즈 노트: fuzzing, generic (0) | 2022.06.23 |
---|---|
golang9 goroutine (0) | 2022.03.02 |
golang8 interface (0) | 2022.02.28 |
golang7 slice (0) | 2022.01.09 |
golang6 구조체 (0) | 2021.12.26 |