티스토리 뷰
개요
Go runtime 과 goroutine 에 대하여 정리해보고자 한다.
- 여기에 scheduler, queue, work stealing 등의 키워드를 둘러보겠음
참고서적: Concurrency in GO http://aladin.kr/p/YLCKv
- 6장. 고루틴과 고 런타임
참고 링크들 (이미지 출처)
- Blog posting: https://www.ardanlabs.com/blog/2018/08/scheduling-in-go-part2.html
- Blog posting: https://rakyll.org/scheduler/
- YouTube: https://youtu.be/Yx6FBsGNOp4
세가지 요소. Process, OS Thread, Goroutine
인터넷의 몇몇 그림들을 보자. 보니깐 더 헷갈리드라
우선 아래 그림을 보자. 세 요소를 간략히 보자
1) M: Machine 를 의미하며 OS Thread 이다. OS Thread 로 부르겠다.
2) P: Logical Process 를 의미하며 Context 로도 불린다. Process 로 부르겠다.
3) G: goroutine 이다.
- goroutine 은 OS Thread 위에서 돌아가며
- OS Thread 가 실제로 실행되려면 Process 하나를 잡고 있어야 한다.
- 각각의 Process 에서 돌아갈 준비가 되어 있는 goroutine 들은 LRQ 에서 대기하고 있는다.
- Local Runable Queue 라고 부르겠다.
- 그리고, Global Runable Queue 가 별도로 있다.
처음 접하시는 분들은 무슨 말인지 이해가 안되실거다 일단 넘어가자.
출처: https://www.ardanlabs.com/blog/2018/08/scheduling-in-go-part2.html
유튜브에 나오는 이미지를 캡처했는데 나는 이 그림이 좀 더 낫다.
반복과 중복이 난무하지만 이런것이 머리속에 남기기에 도움이 된다고 생각한다.
출처: : https://youtu.be/Yx6FBsGNOp4
1) Processors 는 Logical CPU 를 의미하는데 보통 CPU 사양에서 4 core 8 thread 라고 할때의 8 thread 가 의미하는 것이다.
2) OS Threads 는 OS 가 스케쥴링 해주는 하나의 프로그램 수행 공간이라 보면 된다.
3) goroutine 은 Go runtime 이 스케쥴링 해주는 경량의 thread 라고 보면 된다.
1. Process
Context 라고도 한다. 코드가 실행되려면 CPU 와 메모리 공간이 있어야 한다.
진짜 (손에 잡히는 ?) 자원이라고 생각하자
1) Process 개수는 runtime.GOMAXPROCS() 로 설정할 수 있다.
2) logical CPU 개수는 runtime.NumCPU() 로 알아낼 수 있다.
3) 따라서 runtime.GOMAXPROCS(runtime.NumCPU()) 라고 하면
- 프로세스의 개수를 컴퓨터가 실제로 병렬적으로 사용할 수 있는 최대치로 쓰겠다고 하는것이다.
- 언듯 Golang 최신버전에선 이게 불필요하다고도 들었는데 일단 skip 하겠음
logical CPU 란 무엇인가?
컴퓨터의 CPU 사양에 6 core 12 thread 라고 되어 있으면 - 6 core 가 물리적 CPU 개수이고, 12 thread 가 논리적 CPU 개수이다. - 물리적으로는 6개의 core 가 존재해서 6개의 일을 병렬적으로 할 수 있지만 (= 일하는 사람이 6명) - 논리적으로 이걸 최적화 해서 (Intel Hyper Threading, AMD SMT Simultaneous Multi-Threading) - 하나의 일을 하는 중의 빈틈에 다른 일을 해줘서 하나의 core 로 두 개의 일을 (=Thread) 할 수 있게 해주는 것 - 실제로는 core * 2 의 성능이 나오는 것도 아니고, - 또한 프로그램이 여기에 최적화 되어 있어야 한다. → 따라서 진짜 병렬적으로 (=같은 시간에 동시에) 여러 일을 할 수 있는 개수는 12 개가 되는 것이다. |
2. OS Thread
컴퓨터의 CPU core (또는 논리적인 CPU 인 Thread) 는 개수의 한계가 있다. 병렬적으로 돌릴 수 있는 프로그램의 개수 제한이 있다는 거다.
그런데 심지어 싱글코어 에서도 우리는 프로그램을 여러 개 돌린다. 어떻게?
HW 적으로는 개수의 한계가 있지만
SW 적으로 (여기서는 OS 레벨에서) 여러 개의 Thread 를 생성해서 프로그램마다 할당해주는 것이다.
그럼 할당된 Thread (위의 프로그램) 마다 CPU 를 써야하는데 어떻게 할당해주지?
그냥 0.000001 초씩 돌아가며 수행해주면 사람들은 멍청하게도 프로그램이 동시에 수행되는줄 알겠지? (실제 0.000001 초라는 것은 아님)
- 동영상도 보면서 음악도 틀어놓고 웹서핑도 하는거지
- 어짜피 영화도 24장의 정지화면을 빨리 돌려주면 움직이는걸로 착각하잖아 하하
3. Goroutine
고루틴은 OS Thread 랑 같다. 그런데 다르다.
1) 코루틴이다 (비선점형이라고만 알고 넘어가자)
2) Go runtime 이 관리해준다.
- 즉, OS Thread 는 OS 가 생성하고 관리해주는데
- Goroutine 은 Go runtime 이 생성하고 관리해주는 것이다.
여기에서 n:m 이라는 표현이 나온다.
1) 1:1 이라면 1개의 user level thread가 1개의 OS Thread 위에서 돌아가는 거다.
2) n:1 이라면 n개의 user level thread가 1개의 OS Thread 위에서 돌아가는 거다.
3) m:n 이라면 m개의 user level thread가 n개의 OS Thread 위에서 돌아가는 거다.
→ 고언어는 goroutine 이라는 user level thread m개가 n 개의 OS Thread 위에서 돌아가는 것이다.
OS Thread 가 있는데 왜 또 goroutine 을 쓰는 걸까?
한 줄로 말하자면 엄청 경량이다. 가볍게 휙휙 쓰기 좋다는 것이다.
Work stealing. especially continuation stealing
아래 이미지를 보자
1) 프로세스들이 P1, P2 두 개 있고,
2) OS Thread (M) 은 5개가 있다.
- 그런데 살아있는 넘은 두 개이며, 각각 P1, P2 위에서 동작중이다.
3) runnable 한 goroutine 들이 파란 동그라미 들이며,
- 각각 global runable queue 와 local runable queue 에 대기하고 있다가 하나씩 실행되고 있다.
< 출처: https://rakyll.org/scheduler/>
Work stealing 이란 뭔가?
별거 아니다. 만약 P2 가 local queue 에 대기중이 goroutine 을 다 실행하고 나면 할게 없다.
당신이 사장님이면 이녀석을 놀게 놔둘 것인가? 냉큼 일을 더 던져줄 수도 있겠다.
→ 하지만 사랑받는, 눈치빠른 미생의 직원이라면? 적극적으로 다른 Process 들의 queue 를 챙겨보고
대기중인 녀석을 훔쳐서라도 일을 한다. 일을 훔쳤으니깐 work stealing 이다.
Task stealing 과 continuation stealing
work stealing 은 크게 Task stealing 과 continuation stealing 으로 나눌 수 있다.
- task stealing 은 child stealing 으로 부르기도 한다.
1) 결론부터 말하자면 (TL;DR) continuation stealing 이 낫다
2) 그런데 컴파일러가 이걸 지원해줘야 한다.
3) 그런데 Golang 컴파일러는 이걸 지원한다. 하하하
continuation stealing 이 나은 이유
Task stealing 과 continuation stealing 의 동작방식 설명부터 먼저 해줘야 겠지만 우선 이유부터 적어보겠음
1) 고루틴이 분기되었다가 (fork) 합류되는 지점 (join) 에서의 지연이 없으며
2) 동작하는 단계가 조금 더 적다.
3) 동작하는 순서가 sequential 하다. task stealing 은 무작위적임
실제 동작을 말로 설명하고 비교해보기
일일이 그림을 그릴 자신이 없어서 글로 시도해 봄
Process 2개가 있다고 하자. P1, P2
task stealing |
continuation stealing |
1) P1 에서 main routine (=역시 goroutine 이다) 이 시작한다. 2) goroutine 을 실행한다 - 이 시점에 실행해야 할 goroutine 은 2가지 이다. (1) main goroutine 의 남은 코드들 (=continuation) (2) 방금 fork 한 goroutine 3) task stealing 은 P1 에서 main goroutine 의 남은 부분을 계속 실행하고 - goroutine 은 P1 의 대기열, queue 의 맨 꽁무니에 넣어둔다. 4) 한편, 할 일이 없어서 매의 눈으로 다른 Process 의 queue 를 째려보던 P2 는 5) P1 queue 로 들어온 goroutine 을 보게 되고, 잽싸게 task stealing 한다. |
1) P1 에서 main routine (=역시 goroutine 이다) 이 시작한다. 2) goroutine 을 실행한다 - 이 시점에 실행해야 할 goroutine 은 2가지 이다. (1) main goroutine 의 남은 코드들 (=continuation) (2) 방금 fork 한 goroutine (여기까진 똑같음) 3) continuation stealing 은 P1 에서 goroutine 을 실행하고 - main goroutine 을 P1 의 대기열, queue 의 맨 꽁무니에 넣어둔다. 4) 한편, 할 일이 없어서 매의 눈으로 다른 Process 의 queue 를 째려보던 P2 는 5) P1 queue 로 들어온 main goroutine 의 남은 부분인 continuation 을 보게 되고, 잽싸게 task stealing 한다. |
몇 가지만 짚어보자.
1) 잘 실행하다가 goroutine 을 실행하는 시점은 언제일까?
- 딱 그 시점에 실행되었으면 하는 기능이 있는 것이다. 그렇다면 바로 실행해주면 좋다.
- continuation stealing 이 바로 그렇다. 실행하고 있던 main goroutine 을 queue 에 넣어두고, 방금 fork 된 goroutine 을 실행하는 것이다.
→ 이것은 마치 우리가 함수를 호출했을때에 context switching 이 일어나는 것을 떠올리게 한다.
2) goroutine 을 실행한 main goroutine 은 보통 그 다음에 무엇을 할까?
- 보통 머지 않아서 fork 한 goroutine 이 수행을 마치고 join 할때까지 기다리고 있는다.
- 즉 별달리 하는게 없을 가능성이 높다는 거다. 따라서 queue 에 넣어두는 것이 합당해 보인다.
(언급할 타이밍이 없었는데) queue 들은 양방향으로 넣고 뺄 수 있다.
- Process 자신의 queue 에 넣고 뺄때는 tail 쪽을 이용하지만
- stealing 할때는 head 쪽에서 빼내간다.
(참고) GRQ - Global Runable Queue 에 대하여
존재 이유에 대한 설명이 없었다.
- 언제 GRQ 에 넣는지는 생략 (사실 찾다가 일단 넘어감)
스케쥴링을 할 때마다 아래와 같은 과정을 거친다.
링크: https://go.googlesource.com/go/+/master/src/runtime/proc.go
runtime.schedule()
{ |
1) 61번에 1번 정도는 GRQ 에 대기중인 goroutine 이 있는지 체크하고 2) 없으면 local queue 를 체크한다음 3) 거기도 없으면 다른 Process 의 LRQ 를 체크하고 4) 그래도 없으면, 다시 한번 GRQ 체크 5) 마지막으로 network 쪽을 poll 해준다.
|
'golang' 카테고리의 다른 글
Slack Slash Command - timezone current time 1/2 (0) | 2019.09.17 |
---|---|
xid: golang GUID 생성 package 둘러보기 (0) | 2019.08.16 |
Golang 의 동시성을 이용한 소수 찾기 (0) | 2019.07.22 |
Don't communicate by sharing memory, share memory by communicating (0) | 2019.07.19 |
Concurrency Is Not Parallelism (0) | 2019.07.19 |
- Total
- Today
- Yesterday
- 2023
- agile
- 잡학툰
- OpenAI
- websocket
- solid
- github
- folklore
- bun
- 영화
- 체호프
- golang
- ChatGPT
- intellij
- JIRA
- pool
- postgres
- Gin
- Bug
- 독서
- go
- 독서후기
- strange
- 인텔리제이
- 제이펍
- 클린 애자일
- Shortcut
- 노션
- notion
- API
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |