Go Concurrency
Goroutine
- Go runtime에 의해 관리되는 경량화 스레드
- 메인 goroutine이 종료되면 프로그램이 종료됨
Channel
- 서로 다른 goroutine 간 데이터 전달(통신) 수단
make
를 통해 초기화해야 사용 가능- Channel 자체가 내부에서 mutex처럼 동작 -> 별도의 lock 관리 필요 x
- Sender는 버퍼에 값을 넣을 때까지 block됨(asleep 상태)
- Receiver는 버퍼에 값이 들어올 때까지 block됨(asleep 상태)
- 버퍼가 꽉찬 것 = Receiver가 값을 받을때까지 대기중인 상태
ch := make(chan int) // 채널 버퍼 크기 1
ch <- v // 채널 ch에 v를 전송한다.
v := <-ch // ch로 부터 값을 받는다
chs := make(chan int, 2) // 채널 버퍼 크기 2
Deadlock
-
모든 goroutine이 asleep 상태
fatal error: all goroutines are asleep - deadlock!
-
package main import "fmt" func main() { ch := make(chan int, 2) ch <- 1 ch <- 2 ch <- 3 /* fatal error: all goroutines are asleep - deadlock! goroutine 1 [chan send] */ fmt.Println(<-ch) fmt.Println(<-ch) }
현재 goroutine은 메인 goroutine 하나이고, 이미 꽉찬 채널에 3을 넣으려고 하면 메인 goroutine이 block되어 asleep 상태가 됨 => 모든 goroutine이 asleep이므로 데드락
-
package main import "fmt" func main() { ch := make(chan int, 2) ch <- 1 ch <- 2 fmt.Println(<-ch) fmt.Println(<-ch) fmt.Println(<-ch) /* fatal error: all goroutines are asleep - deadlock! goroutine 1 [chan receive] */ }
위와 마찬가지로 메인 goroutine은 하나이고, 빈 채널에서 값을 가져오려고 하면 메인 goroutine이 채널에 값이 들어올 때까지 asleep 상태가 됨 => 모든 goroutine이 asleep이므로 데드락
Range and Close
package main
import (
"fmt"
)
func fibonacci(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c) // (1)
}
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
for i := range c {
fmt.Println(i)
}
}
-
close(c) : 더 이상 채널 c에 값을 넣지 않겠다고 하는 것
- sender가 호출하는게 자연스럽고 그래야한다.
- receiver가 호출 시 sender가 이미 close된 채널에 값을 넣을 수 있고 close된 채널에 값을 넣을시
panic
발생
-
range c : 채널 c의 receiver 역할
-
v, ok := <- ch
이때,
ok == ( ch에 값이 존재 || !(ch이 closed) )
즉, ch에 값이 하나도 없고, ch이 close되었을때만 false
-
range c 는 위의 ok값이 false가 될 때까지 계속 값을 가져옴
-
for v := range c for v,ok <-ch; ok // 같은 표현
-
따라서 range loop을 벗어나려면 반드시 close 해주어야함.
- (1)을 주석처리하면
fatal error: all goroutines are asleep - deadlock!
발생(유일한 goroutine인 메인 goroutine이 range문에서 asleep상태가 되기 때문) - range를 사용하는 등의 상황이 아니면 파일처럼 꼭 닫아줄 필요는 없음
- (1)을 주석처리하면
-
Select
- 케이스 하나가 runnable할 때까지 block 됨
package main
import "fmt"
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() { // 익명함수
for i := 0; i < 5; i++ {
fmt.Print(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}
// 결과 01123quit
- case로 default도 지정 가능
sync.Mutex
-
공유 자원에 대해 하나의 goroutine만 접근가능하도록
-
해당로직의 앞뒤로 lock & unlock
-
var mutex sync.Mutex mutex.Lock() doSomethingWithSharedResource() mutex.Unlock()
-
-
package main import ( "fmt" "sync" "time" ) // SafeCounter is safe to use concurrently. type SafeCounter struct { mu sync.Mutex v map[string]int } func (c *SafeCounter) Inc(key string) { c.mu.Lock() // Lock so only one goroutine at a time can access the map c.v. c.v[key]++ c.mu.Unlock() } func (c *SafeCounter) Value(key string) int { c.mu.Lock() // Lock so only one goroutine at a time can access the map c.v. defer c.mu.Unlock() return c.v[key] } func main() { c := SafeCounter{v: make(map[string]int)} for i := 0; i < 1000; i++ { go c.Inc("somekey") } time.Sleep(time.Second) fmt.Println(c.Value("somekey")) }
Leave a comment