Ch13. Concurrency
동시성과 깔끔한 코드는 양립하기 어렵다!
멀티스레드 코드는 시스템이 부하를 받기 전까지는 멀쩡하게 돌아가지만, 소수의 케이스에서 이슈가 발생함
- 두개의 스레드 기준으로 잠재적인 경로는 최대 12870개
- long일 경우 2704156
멀티스레드 프로그래밍에서 어떻게 하면 코드를 깨끗하고 효율적으로 짤 수 있는가에 대한 내용
동시성
- 디커플링 전략
- 무엇과 언제를 분리
- 구조적 개선
- 각 스레드는 다른 스레드와 무관하게 돌아감
- 응답 시간과 작업 처리량 개선
- 항상 성능을 높여주는 것은 아님
- 설계 변할 수 있음
- 동시성에 대해 이해해야함
- 복잡함
- 버그 재현 어려움
- 설계 전략을 근본적으로 다시 바라봐야함
동시성 방어원칙
- SRP
- 반복적으로 나오는 중요한 포인트
- 동시성과 관련없는 코드는 분리해야함
- Critical section
- 일반적으로 synchronized로 보호함
- 최대한 줄여라
- 스레드 테스트 코드
- 재현 매우 어려움
- 일회성 오류를 그냥 넘기면 안됨
- 일단 멀티 스레드랑 관련없는 외부 코드부터 잘 돌아야 함!
- 다양한 설정에서 돌려봐야함
- 보조코드
- 실패하는 경로가 실행될 확률이 극도로 저조함
- jiggle(흔들다)
- 무작위로 nop, sleep 이나 yield를 수행
- 서비스 환경에 바로 넣을 수는 없기 때문에 같은 이름의 두개의 메서드를 생성
- 보조코드
→ 이전에 나온 내용대로 코드를 잘 분리하고 잘 짜고, 동시성에 대해 잘 이해하고 있으면 됨!!
Golang 예제
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package main
import (
"fmt"
"time"
)
func sendHello(ch1 chan string) {
time.Sleep(time.Second * 1)
ch1 <- "hello"
}
func sendWorld(ch2 chan string) {
time.Sleep(time.Second * 3)
ch2 <- " world"
}
func receive(ch1 chan string, ch2 chan string, done chan string) {
var msg string
for {
select {
case msg1 := <-ch1:
fmt.Println("msg: ", msg1)
msg += msg1
case msg2 := <-ch2:
fmt.Println("msg: ", msg2)
msg += msg2
done <- msg
return
default:
fmt.Println("default는 계속 호출됩니다.")
time.Sleep(time.Second)
}
}
}
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
done := make(chan string)
go sendHello(ch1)
go sendWorld(ch2)
go receive(ch1, ch2, done)
result := <-done
fmt.Println("result: ", result)
}
===
output
default는 계속 호출됩니다.
default는 계속 호출됩니다.
msg: hello
default는 계속 호출됩니다.
msg: world
result: hello world