Go 채널 잠금 해제: 작동 방식
심층 Golang 채널: 구현 원칙 및 성능 최적화 제안
Golang의 채널은 CSP 동시성 모델의 핵심 구성 요소이자 Goroutine 간의 통신을 위한 브리지입니다. 채널은 Golang에서 자주 사용되며 내부 구현 원칙을 깊이 이해하는 것이 중요합니다. 이 기사에서는 Go 1.13 소스 코드를 기반으로 Channel의 기본 구현을 분석합니다.
채널 기본 사용법
채널 구현을 공식적으로 분석하기 전에 기본 사용법을 검토해 보겠습니다.
package main import "fmt" func main() { c := make(chan int) go func() { c <- 1 // 发送操作 }() x := <-c // 接收操作 fmt.Println(x) }
이 코드는 채널의 두 가지 기본 작업을 보여줍니다.
- 보내기 작업:
c <- 1
- 수신 작업:
x := <-c
채널은 버퍼링된 채널과 버퍼링되지 않은 채널로 구분됩니다. 위의 코드는 버퍼링되지 않은 채널을 사용합니다. 버퍼링되지 않은 채널에서 다른 고루틴이 현재 데이터를 수신하고 있지 않으면 발신자는 send 문에서 차단됩니다.
채널을 초기화할 때 버퍼 크기를 지정할 수 있습니다. 예를 들어 make(chan int, 2)
에서는 버퍼 크기를 2로 지정합니다. 버퍼가 가득 차기 전에 송신자는 수신자가 준비될 때까지 기다리지 않고 차단하지 않고 데이터를 보낼 수 있습니다. 그러나 버퍼가 가득 차면 발신자는 여전히 차단됩니다.
채널 기본 구현 기능
채널 소스 코드를 살펴보기 전에 Golang에서 채널의 구체적인 구현 위치를 찾아야 합니다. 채널을 사용하면 runtime.makechan
, runtime.chansend
, runtime.chanrecv
등의 기본 함수가 실제로 호출됩니다.
go tool compile -N -l -S hello.go
명령을 사용하여 코드를 어셈블리 지침으로 변환하거나 온라인 도구 컴파일러 탐색기(예: go.godbolt.org/z/3xw5Cj)를 사용할 수 있습니다. 조립 지침을 분석하여 다음을 찾을 수 있습니다.
make(chan int)
은runtime.makechan
기능에 해당합니다.c <- 1
은runtime.chansend
기능에 해당합니다.x := <-c
은runtime.chanrecv
기능에 해당합니다.
이러한 기능의 구현은 Go 소스 코드의 runtime/chan.go
파일에 있습니다.
채널 구조
make(chan int)
은 컴파일러에 의해 runtime.makechan
함수로 변환되며 해당 함수 서명은 다음과 같습니다.
func makechan(t *chantype, size int) *hchan
그 중 t *chantype
은 채널 요소 유형이고, size int
은 사용자가 지정한 버퍼 크기(지정하지 않은 경우 0)이며 반환 값은 *hchan
입니다. hchan
은 Golang의 Channel 내부 구현 구조로 다음과 같이 정의됩니다.
type hchan struct { qcount uint // 缓冲区中已放入元素的数量 dataqsiz uint // 用户构造Channel时指定的缓冲区大小 buf unsafe.Pointer // 缓冲区 elemsize uint16 // 缓冲区中每个元素的大小 closed uint32 // Channel是否关闭,==0表示未关闭 elemtype *_type // Channel元素的类型信息 sendx uint // 缓冲区中发送元素的索引位置(发送索引) recvx uint // 缓冲区中接收元素的索引位置(接收索引) recvq waitq // 等待接收的Goroutine列表 sendq waitq // 等待发送的Goroutine列表 lock mutex }
hchan
의 속성은 대략 세 가지 범주로 나뉩니다.
- 버퍼 관련 속성:
buf
,dataqsiz
,qcount
등 Channel의 버퍼 크기가 0이 아닌 경우, 수신할 데이터를 저장하는 버퍼로 사용되며, 링 버퍼를 이용하여 구현된다. - 대기 대기열 관련 속성:
recvq
에는 데이터 수신을 기다리는 고루틴이 포함되고,sendq
에는 데이터 전송을 기다리는 고루틴이 포함됩니다.waitq
이중 연결 리스트를 사용하여 구현되었습니다. - 기타 속성:
lock
,elemtype
,closed
등
makechan
함수는 주로 일부 합법성 검사와 버퍼 및 hchan
과 같은 속성의 메모리 할당을 수행하는데, 이에 대해서는 여기서 자세히 설명하지 않습니다.
hchan
속성을 간단히 분석해 보면 버퍼와 대기 큐라는 두 가지 중요한 구성 요소가 있음을 알 수 있습니다. hchan
의 모든 동작과 구현은 이 두 구성 요소를 중심으로 이루어집니다.
채널 데이터 전송
채널의 전송 및 수신 프로세스는 매우 유사합니다. 먼저 채널(예: c <- 1
)의 전송 프로세스를 분석합니다.
가 채널에 데이터를 보내려고 할 때 recvq
대기열이 비어 있지 않으면 데이터 수신을 기다리고 있는 고루틴이 recvq
헤더에서 제거되고 데이터가 고루틴으로 직접 전송됩니다. 코드는 다음과 같습니다.
package main import "fmt" func main() { c := make(chan int) go func() { c <- 1 // 发送操作 }() x := <-c // 接收操作 fmt.Println(x) }
recvq
데이터 수신을 기다리는 고루틴이 포함되어 있습니다. 고루틴이 수신 작업(예: x := <-c
)을 사용할 때 sendq
가 이때 비어 있지 않으면 sendq
에서 고루틴을 가져오고 데이터가 해당 고루틴으로 전송됩니다.
recvq
이 비어 있으면 현재 데이터 수신을 기다리고 있는 고루틴이 없으며 채널이 데이터를 버퍼에 넣으려고 시도한다는 의미입니다.
func makechan(t *chantype, size int) *hchan
이 코드의 기능은 매우 간단합니다. 데이터를 버퍼에 넣는 것입니다. 이 프로세스에는 링 버퍼의 작업이 포함되며, dataqsiz
은 사용자가 지정한 버퍼 크기를 나타냅니다(지정하지 않은 경우 기본값은 0).
버퍼되지 않은 채널을 사용하거나 버퍼가 가득 찬 경우(c.qcount == c.dataqsiz
), 전송할 데이터와 현재 고루틴은 sudog
객체로 패키징되어 sendq
에 배치되고, 현재 고루틴은 대기 상태로 설정됩니다:
type hchan struct { qcount uint // 缓冲区中已放入元素的数量 dataqsiz uint // 用户构造Channel时指定的缓冲区大小 buf unsafe.Pointer // 缓冲区 elemsize uint16 // 缓冲区中每个元素的大小 closed uint32 // Channel是否关闭,==0表示未关闭 elemtype *_type // Channel元素的类型信息 sendx uint // 缓冲区中发送元素的索引位置(发送索引) recvx uint // 缓冲区中接收元素的索引位置(接收索引) recvq waitq // 等待接收的Goroutine列表 sendq waitq // 等待发送的Goroutine列表 lock mutex }
goparkunlock
은 입력 뮤텍스를 잠금 해제하고 현재 고루틴을 일시 중지하여 대기 상태로 설정합니다. gopark
과 goready
은 쌍으로 나타나며 상호 연산입니다.
사용자 입장에서는 gopark
호출 후 데이터 전송을 위한 코드문이 차단됩니다.
채널 데이터 수신
채널의 수신 과정은 기본적으로 전송 과정과 유사하므로 여기서는 자세히 설명하지 않겠습니다. 수신 과정에서 발생하는 버퍼 관련 동작에 대해서는 뒤에서 자세히 설명한다.
runtime.mutex
을 사용하면 채널의 전체 송수신 과정이 잠겨 있으므로 주의해야 합니다. runtime.mutex
은 런타임 관련 소스 코드에서 일반적으로 사용되는 경량 잠금입니다. 전체 프로세스가 가장 효율적인 잠금 없는 솔루션은 아닙니다. Golang의 잠금 없는 채널에 관한 문제가 있습니다: go/issues#8899.
채널 링 버퍼 구현
채널은 링 버퍼를 사용하여 작성된 데이터를 캐시합니다. 링 버퍼에는 많은 장점이 있으며 고정 길이 FIFO 대기열을 구현하는 데 이상적입니다.
채널의 링 버퍼 구현은 다음과 같습니다.
hchan
에는 버퍼 관련 변수가 두 개 있는데, recvx
과 sendx
입니다. sendx
은 버퍼에 쓰기 가능한 인덱스를 나타내고, recvx
는 버퍼에 읽기 가능한 인덱스를 나타냅니다. recvx
과 sendx
사이의 요소는 정상적으로 버퍼에 담긴 데이터를 나타냅니다.

buf[recvx]
을 사용하여 큐의 첫 번째 요소를 직접 읽을 수 있고, buf[sendx] = x
을 사용하여 해당 요소를 큐의 끝에 넣을 수 있습니다.
버퍼 쓰기
버퍼가 가득 차지 않은 경우, 버퍼에 데이터를 넣는 동작은 다음과 같습니다.
package main import "fmt" func main() { c := make(chan int) go func() { c <- 1 // 发送操作 }() x := <-c // 接收操作 fmt.Println(x) }
chanbuf(c, c.sendx)
은 c.buf[c.sendx]
과 동일합니다. 위의 과정은 매우 간단합니다. 데이터를 버퍼 위치sendx
에 복사하기만 하면 됩니다.
그런 다음 sendx
을 다음 위치로 이동하세요. sendx
이 마지막 위치에 도달하면 0으로 설정되는데, 이는 일반적인 end-to-end 접근 방식입니다.
버퍼 읽기
버퍼가 가득 차지 않으면 sendq
도 비어 있어야 합니다(왜냐하면 버퍼가 가득 차지 않으면 데이터를 보내는 고루틴이 대기열에 들어가지 않고 데이터를 직접 버퍼에 넣기 때문입니다). 이때 채널 chanrecv
의 읽기 로직은 비교적 간단합니다. 버퍼에서 직접 데이터를 읽을 수도 있으며, 이는 기본적으로 위의 버퍼 쓰기와 동일합니다. recvx
에 대기 중인 고루틴이 있으면 이때 버퍼가 가득 차 있어야 합니다. 이때 채널의 읽기 로직은 다음과 같습니다. sendq
func makechan(t *chantype, size int) *hchan
은 데이터를 받는 변수에 해당하는 주소입니다(예를 들어 ep
에서 x := <-c
은 ep
의 주소입니다). x
은 sg
에서 가져온 첫 번째 sendq
를 나타냅니다. 코드에서: sudog
- 은 버퍼에서 현재 읽을 수 있는 요소를 수신 변수의 주소에 복사하는 것을 의미합니다.
typedmemmove(c.elemtype, ep, qp)
- 은
typedmemmove(c.elemtype, qp, sg.elem)
에서 고루틴이 전송하기를 기다리는 데이터를 버퍼에 복사하는 것을 의미합니다.sendq
은 나중에 실행되기 때문에 대기열 끝에recv
에 데이터를 배치하는 것과 같습니다.sendq
의 요소를 대기열 끝에 복사하여 FIFO(선입선출)를 구현합니다. . sendq
요약
채널은 Golang에서 가장 일반적으로 사용되는 기능 중 하나입니다. 소스 코드를 이해하면 채널을 더 잘 사용하고 이해하는 데 도움이 됩니다. 동시에 지나치게 미신을 믿지 말고 채널의 성능에 의존하지 마십시오. 현재 채널의 디자인에는 여전히 최적화할 여지가 많습니다.
최적화 제안:
- 성능을 향상하려면 더 가벼운 잠금 메커니즘이나 잠금 없는 방식을 사용하세요.
- 버퍼 관리를 최적화하고 메모리 할당 및 복사 작업을 줄입니다.
Leapcell: Golang 웹 애플리케이션을 위한 최고의 서버리스 플랫폼

마지막으로 Go 서비스 배포에 매우 적합한 플랫폼을 추천합니다: Leapcell
- 다국어 지원: JavaScript, Python, Go 또는 Rust 개발을 지원합니다.
- 무제한 프로젝트를 무료로 배포: 사용한 만큼만 비용을 지불하고 요청이나 수수료가 없습니다.
- 매우 비용 효율적: 사용한 만큼만 지불하고 유휴 수수료가 없습니다. 예를 들어 $25는 평균 응답 시간이 60밀리초인 694만 개의 요청을 지원합니다.
- 원활한 개발자 환경: 손쉬운 설정을 위한 직관적인 UI, 실행 가능한 통찰력을 위한 완전 자동화된 CI/CD 파이프라인 및 GitOps 통합.
- 쉬운 확장성 및 고성능: 자동으로 확장하여 높은 동시성을 쉽게 처리하고 운영 오버헤드가 없으며 구축에 집중합니다.

자세한 내용은 설명서를 확인하세요!
Leapcell 트위터: https://www.php.cn/link/7884effb9452a6d7a7a79499ef854afd
위 내용은 Go 채널 잠금 해제: 작동 방식의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

Video Face Swap
완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

뜨거운 주제











Go Language는 효율적이고 확장 가능한 시스템을 구축하는 데 잘 작동합니다. 장점은 다음과 같습니다. 1. 고성능 : 기계 코드로 컴파일, 빠른 달리기 속도; 2. 동시 프로그래밍 : 고어 라틴 및 채널을 통한 멀티 태스킹 단순화; 3. 단순성 : 간결한 구문, 학습 및 유지 보수 비용 절감; 4. 크로스 플랫폼 : 크로스 플랫폼 컴파일, 쉬운 배포를 지원합니다.

Golang은 동시성에서 C보다 낫고 C는 원시 속도에서 Golang보다 낫습니다. 1) Golang은 Goroutine 및 Channel을 통해 효율적인 동시성을 달성하며, 이는 많은 동시 작업을 처리하는 데 적합합니다. 2) C 컴파일러 최적화 및 표준 라이브러리를 통해 하드웨어에 가까운 고성능을 제공하며 극도의 최적화가 필요한 애플리케이션에 적합합니다.

Golang과 C는 각각 공연 경쟁에서 고유 한 장점을 가지고 있습니다. 1) Golang은 높은 동시성과 빠른 발전에 적합하며 2) C는 더 높은 성능과 세밀한 제어를 제공합니다. 선택은 프로젝트 요구 사항 및 팀 기술 스택을 기반으로해야합니다.

Golang은 성능과 확장 성 측면에서 Python보다 낫습니다. 1) Golang의 컴파일 유형 특성과 효율적인 동시성 모델은 높은 동시성 시나리오에서 잘 수행합니다. 2) 해석 된 언어로서 파이썬은 천천히 실행되지만 Cython과 같은 도구를 통해 성능을 최적화 할 수 있습니다.

Golang과 Python은 각각 고유 한 장점이 있습니다. Golang은 고성능 및 동시 프로그래밍에 적합하지만 Python은 데이터 과학 및 웹 개발에 적합합니다. Golang은 동시성 모델과 효율적인 성능으로 유명하며 Python은 간결한 구문 및 풍부한 라이브러리 생태계로 유명합니다.

C는 하드웨어 리소스 및 고성능 최적화가 직접 제어되는 시나리오에 더 적합하지만 Golang은 빠른 개발 및 높은 동시성 처리가 필요한 시나리오에 더 적합합니다. 1.C의 장점은 게임 개발과 같은 고성능 요구에 적합한 하드웨어 특성 및 높은 최적화 기능에 가깝습니다. 2. Golang의 장점은 간결한 구문 및 자연 동시성 지원에 있으며, 이는 동시성 서비스 개발에 적합합니다.

goimpactsdevelopmentpositively throughlyspeed, 효율성 및 단순성.

Golang과 C의 성능 차이는 주로 메모리 관리, 컴파일 최적화 및 런타임 효율에 반영됩니다. 1) Golang의 쓰레기 수집 메커니즘은 편리하지만 성능에 영향을 줄 수 있습니다. 2) C의 수동 메모리 관리 및 컴파일러 최적화는 재귀 컴퓨팅에서 더 효율적입니다.
