제공하는 동시성 제어를 위한 자료구조를 TCP 서버 구현에 넣어보자! - 서버를 구현하면서 발생하는 동시성 문제를 해결해보기 golang 내부 동작원리 학습(이론 + 시간되면 golang/go 코드 분석) - 해당 자료구조 내부는 어떻게 동작할까? - golang 내부는 어떻게 동작할까? 대략적인 발표 흐름
구현 2. TCP 서버 라이브러리화 3. select문 순위 지정 & 이벤트루프 구현 4. broadcast 서버 구현 2부: golang runtime 내부 1. golang runtime 스케줄링 2. 컨테이너 자료형의 가용성과 일관성 3. 채널 송수신 및 select문 동작 원리
하나의 커넥션의 장애로 인해 다른 커넥션이 대기하고 있게 된다면? write pending -> read pending -> conn read pending 밀림이 전파된다!! TCP 서버 라이브러리화 eventloop를 블로킹하면 안되는 이유(라인) https://engineering.linecorp.com/ko/blog/do-not-block-the-event-loop-part3
g local runnable queue local runnable queue main goroutine g 1. newproc으로 goroutine 생성 후 queue에 삽입 2. wakep 호출로 다른 p 활성화 3. p에서 실행할 작업 탐색(findrunnable)
grq 확인 lrq 확인 netpoller 확인(네트워크) 다른 p에 대한 작업 훔치기 findrunnable 내부 findrunnable https://github.com/golang/go/blob/beaf7f3282c2548267d3c894417cc4ecacc5d575/src/runtime/proc.go#L3249
M g local runnable queue local runnable queue main goroutine g 1. newproc으로 goroutine 생성 후 queue에 삽입 2. wakep 호출로 다른 p 활성화 4. stealWork로부터 작업 훔치기 3. p에서 실행할 작업 탐색(findrunnable) golang runtime scheduler 내부 구조
M g local runnable queue local runnable queue main goroutine g 1. newproc으로 goroutine 생성 후 queue에 삽입 2. wakep 호출로 다른 p 활성화 4. stealWork로부터 작업 훔치기 g 5. 고루틴 실행 3. p에서 실행할 작업 탐색(findrunnable) golang runtime scheduler 내부 구조
n FIFO? FIFO만 쓰면 되는거 아닌가? > 최근 생성된 고루틴 메모리를 재사용하여 캐시 히트를 높이기 위해 2. 왜 lrq쓰는지? grq만 쓰면 되는 거 아닌가? > grq에 대한 락 경합을 줄이기 위해 3. 왜 p가 존재하는지? 쓰레드 + 고루틴으로 충분하지 않나? > 실행 가능한 쓰레드의 블로킹 쓰레드에 대한 작업 훔치기 횟수를 줄이기 위해
수신하거나 일관성을 보장할 수 없는 경우 오류 발생 가용성(A): 모든 요청은 노드가 다운되거나 사용할 수 없는 경우에도 오류 없는 응답을 받음 파티션 내성(P): 노드 간 임의의 수의 메세지가 손실되더라도 시스템 지속 작동 https://docs.aws.amazon.com/ko_kr/whitepapers/latest/availability-and-beyond-improving-resilience/cap-theorem. html
replica를 구성하고 락을 획득하지 않고 callback을 실행시킨다! 실제 sync.Map은 내부에 read와 write를 분리 (*sync.Map)Range https://github.com/golang/go/blob/4e548f2c8e489a408033c8aab336077b16bc8cf7/src/sync/map.go#L449
{ m1.Lock() defer m1.Unlock() m2.Lock() defer m2.Unlock() fmt.Println("m1m2") }() go func() { m2.Lock() defer m2.Unlock() m1.Lock() defer m1.Unlock() fmt.Println("m2m1") }() } 1. 상호 배제 2. 점유 대기 1 2 하나의 자원을 획득하고 다른 자원 획득 대기
{ m1.Lock() defer m1.Unlock() m2.Lock() defer m2.Unlock() fmt.Println("m1m2") }() go func() { m2.Lock() defer m2.Unlock() m1.Lock() defer m1.Unlock() fmt.Println("m2m1") }() } 1. 상호 배제 2. 점유 대기 1 2 4. 상호 대기 X 3. 비선점 1 -> 2 점유 자원 대기 2 -> 1 점유 자원 대기
go func() { select { case <-c1: fmt.Println("g1 c1") case c2 <- true: fmt.Println("g1 c2") } }() go func() { select { case <-c2: fmt.Println("g2 c1") case c1 <- true: fmt.Println("g2 c2") } }() } 안전한 코드일까? case가 순서대로 실행: 데드락 가능성 존재 case가 랜덤으로 실행: 데드락 가능성 존재 실제는 case가 랜덤으로 실행
go func() { select { case <-c1: fmt.Println("g1 c1") case c2 <- true: fmt.Println("g1 c2") } }() go func() { select { case <-c2: fmt.Println("g2 c1") case c1 <- true: fmt.Println("g2 c2") } }() } 안전한 코드일까? 그럼 select마다 채널 어떻게 대기하는지 다 고려해줘야 하나!!
go func() { select { case <-c1: fmt.Println("g1 c1") case c2 <- true: fmt.Println("g1 c2") } }() go func() { select { case <-c2: fmt.Println("g2 c1") case c1 <- true: fmt.Println("g2 c2") } }() } 두 select 구문은 내부 케이스에서 c1, c2의 메모리가 같은 순서로 정렬되어 락을 획득하기 때문에 데드락X 1 2
이론을 통한 네트워크 동기화 기법(넥슨) https://youtu.be/j3eQNm-Wk04?si=ABZoagbk6ECTVp0C why discord is switching from Go to Rust(discord) https://discord.com/blog/why-discord-is-switching-from-go-to-rust CNCF landscape(CNCF) https://landscape.cncf.io/ 추가자료