> 백엔드 개발 > Golang > Go 채널 잠금 해제: 작동 방식

Go 채널 잠금 해제: 작동 방식

Mary-Kate Olsen
풀어 주다: 2025-01-17 02:11:10
원래의
355명이 탐색했습니다.

심층 Golang 채널: 구현 원칙 및 성능 최적화 제안

Golang의 채널은 CSP 동시성 모델의 핵심 구성 요소이자 Goroutine 간의 통신을 위한 브리지입니다. 채널은 Golang에서 자주 사용되며 내부 구현 원칙을 깊이 이해하는 것이 중요합니다. 이 기사에서는 Go 1.13 소스 코드를 기반으로 Channel의 기본 구현을 분석합니다.

채널 기본 사용법

채널 구현을 공식적으로 분석하기 전에 기본 사용법을 검토해 보겠습니다.

<code class="language-go">package main
import "fmt"

func main() {
    c := make(chan int)

    go func() {
        c <- 1 // 发送操作
    }()

    x := <-c // 接收操作
    fmt.Println(x)
}</code>
로그인 후 복사
로그인 후 복사
로그인 후 복사

이 코드는 채널의 두 가지 기본 작업을 보여줍니다.

  • 보내기 작업: 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 <- 1runtime.chansend 기능에 해당합니다.
  • x := <-cruntime.chanrecv 기능에 해당합니다.

이러한 기능의 구현은 Go 소스 코드의 runtime/chan.go 파일에 있습니다.

채널 구조

make(chan int)은 컴파일러에 의해 runtime.makechan 함수로 변환되며 해당 함수 서명은 다음과 같습니다.

<code class="language-go">func makechan(t *chantype, size int) *hchan</code>
로그인 후 복사
로그인 후 복사
로그인 후 복사

그 중 t *chantype은 채널 요소 유형이고, size int은 사용자가 지정한 버퍼 크기(지정하지 않은 경우 0)이며 반환 값은 *hchan입니다. hchan은 Golang의 Channel 내부 구현 구조로 다음과 같이 정의됩니다.

<code class="language-go">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
}</code>
로그인 후 복사
로그인 후 복사

hchan의 속성은 대략 세 가지 범주로 나뉩니다.

  • 버퍼 관련 속성: buf, dataqsiz, qcount 등 Channel의 버퍼 크기가 0이 아닌 경우, 수신할 데이터를 저장하는 버퍼로 사용되며, 링 버퍼를 이용하여 구현된다.
  • 대기 대기열 관련 속성: recvq에는 데이터 수신을 기다리는 고루틴이 포함되고, sendq에는 데이터 전송을 기다리는 고루틴이 포함됩니다. waitq이중 연결 리스트를 사용하여 구현되었습니다.
  • 기타 속성: lock, elemtype, closed

makechan 함수는 주로 일부 합법성 검사와 버퍼 및 hchan과 같은 속성의 메모리 할당을 수행하는데, 이에 대해서는 여기서 자세히 설명하지 않습니다.

hchan 속성을 ​​간단히 분석해 보면 버퍼와 대기 큐라는 두 가지 중요한 구성 요소가 있음을 알 수 있습니다. hchan의 모든 동작과 구현은 이 두 구성 요소를 중심으로 이루어집니다.

채널 데이터 전송

채널의 전송 및 수신 프로세스는 매우 유사합니다. 먼저 채널(예: c <- 1)의 전송 프로세스를 분석합니다.

가 채널에 데이터를 보내려고 할 때 recvq 대기열이 비어 있지 않으면 데이터 수신을 기다리고 있는 고루틴이 recvq 헤더에서 제거되고 데이터가 고루틴으로 직접 전송됩니다. 코드는 다음과 같습니다.

<code class="language-go">package main
import "fmt"

func main() {
    c := make(chan int)

    go func() {
        c <- 1 // 发送操作
    }()

    x := <-c // 接收操作
    fmt.Println(x)
}</code>
로그인 후 복사
로그인 후 복사
로그인 후 복사

recvq 데이터 수신을 기다리는 고루틴이 포함되어 있습니다. 고루틴이 수신 작업(예: x := <-c)을 사용할 때 sendq가 이때 비어 있지 않으면 sendq에서 고루틴을 가져오고 데이터가 해당 고루틴으로 전송됩니다.

recvq이 비어 있으면 현재 데이터 수신을 기다리고 있는 고루틴이 없으며 채널이 데이터를 버퍼에 넣으려고 시도한다는 의미입니다.

<code class="language-go">func makechan(t *chantype, size int) *hchan</code>
로그인 후 복사
로그인 후 복사
로그인 후 복사

이 코드의 기능은 매우 간단합니다. 데이터를 버퍼에 넣는 것입니다. 이 프로세스에는 링 버퍼의 작업이 포함되며, dataqsiz은 사용자가 지정한 버퍼 크기를 나타냅니다(지정하지 않은 경우 기본값은 0).

버퍼되지 않은 채널을 사용하거나 버퍼가 가득 찬 경우(c.qcount == c.dataqsiz), 전송할 데이터와 현재 고루틴은 sudog 객체로 패키징되어 sendq에 배치되고, 현재 고루틴은 대기 상태로 설정됩니다:

<code class="language-go">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
}</code>
로그인 후 복사
로그인 후 복사

goparkunlock은 입력 뮤텍스를 잠금 해제하고 현재 고루틴을 일시 중지하여 대기 상태로 설정합니다. goparkgoready은 쌍으로 나타나며 상호 연산입니다.

사용자 입장에서는 gopark 호출 후 데이터 전송을 위한 코드문이 차단됩니다.

채널 데이터 수신

채널의 수신 과정은 기본적으로 전송 과정과 유사하므로 여기서는 자세히 설명하지 않겠습니다. 수신 과정에서 발생하는 버퍼 관련 동작에 대해서는 뒤에서 자세히 설명한다.

runtime.mutex을 사용하면 채널의 전체 송수신 과정이 잠겨 있으므로 주의해야 합니다. runtime.mutex은 런타임 관련 소스 코드에서 일반적으로 사용되는 경량 잠금입니다. 전체 프로세스가 가장 효율적인 잠금 없는 솔루션은 아닙니다. Golang의 잠금 없는 채널에 관한 문제가 있습니다: go/issues#8899.

채널 링 버퍼 구현

채널은 링 버퍼를 사용하여 작성된 데이터를 캐시합니다. 링 버퍼에는 많은 장점이 있으며 고정 길이 FIFO 대기열을 구현하는 데 이상적입니다.

채널의 링 버퍼 구현은 다음과 같습니다.

hchan에는 버퍼 관련 변수가 두 개 있는데, recvxsendx입니다. sendx은 버퍼에 쓰기 가능한 인덱스를 나타내고, recvx는 버퍼에 읽기 가능한 인덱스를 나타냅니다. recvxsendx 사이의 요소는 정상적으로 버퍼에 담긴 데이터를 나타냅니다.

Go Channel Unlocked: How They Work

buf[recvx]을 사용하여 큐의 첫 번째 요소를 직접 읽을 수 있고, buf[sendx] = x을 사용하여 해당 요소를 큐의 끝에 넣을 수 있습니다.

버퍼 쓰기

버퍼가 가득 차지 않은 경우, 버퍼에 데이터를 넣는 동작은 다음과 같습니다.

<code class="language-go">package main
import "fmt"

func main() {
    c := make(chan int)

    go func() {
        c <- 1 // 发送操作
    }()

    x := <-c // 接收操作
    fmt.Println(x)
}</code>
로그인 후 복사
로그인 후 복사
로그인 후 복사

chanbuf(c, c.sendx)c.buf[c.sendx]과 동일합니다. 위의 과정은 매우 간단합니다. 데이터를 버퍼 위치sendx에 복사하기만 하면 됩니다.

그런 다음 sendx을 다음 위치로 이동하세요. sendx이 마지막 위치에 도달하면 0으로 설정되는데, 이는 일반적인 end-to-end 접근 방식입니다.

버퍼 읽기

버퍼가 가득 차지 않으면 sendq도 비어 있어야 합니다(왜냐하면 버퍼가 가득 차지 않으면 데이터를 보내는 고루틴이 대기열에 들어가지 않고 데이터를 직접 버퍼에 넣기 때문입니다). 이때 채널 chanrecv의 읽기 로직은 비교적 간단합니다. 버퍼에서 직접 데이터를 읽을 수도 있으며, 이는 기본적으로 위의 버퍼 쓰기와 동일합니다. recvx

에 대기 중인 고루틴이 있으면 이때 버퍼가 가득 차 있어야 합니다. 이때 채널의 읽기 로직은 다음과 같습니다. sendq

<code class="language-go">func makechan(t *chantype, size int) *hchan</code>
로그인 후 복사
로그인 후 복사
로그인 후 복사

은 데이터를 받는 변수에 해당하는 주소입니다(예를 들어 ep에서 x := <-cep의 주소입니다). xsg에서 가져온 첫 번째 sendq를 나타냅니다. 코드에서: sudog

  • 은 버퍼에서 현재 읽을 수 있는 요소를 수신 변수의 주소에 복사하는 것을 의미합니다. typedmemmove(c.elemtype, ep, qp)
  • typedmemmove(c.elemtype, qp, sg.elem)에서 고루틴이 전송하기를 기다리는 데이터를 버퍼에 복사하는 것을 의미합니다. sendq은 나중에 실행되기 때문에 대기열 끝에 recv 에 데이터를 배치하는 것과 같습니다. sendq
간단히 말하면 여기서 Channel은 버퍼의 첫 번째 데이터를 해당 수신 변수에 복사하는 동시에

의 요소를 대기열 끝에 복사하여 FIFO(선입선출)를 구현합니다. . sendq

요약

채널은 Golang에서 가장 일반적으로 사용되는 기능 중 하나입니다. 소스 코드를 이해하면 채널을 더 잘 사용하고 이해하는 데 도움이 됩니다. 동시에 지나치게 미신을 믿지 말고 채널의 성능에 의존하지 마십시오. 현재 채널의 디자인에는 여전히 최적화할 여지가 많습니다.

최적화 제안:

  • 성능을 향상하려면 더 가벼운 잠금 메커니즘이나 잠금 없는 방식을 사용하세요.
  • 버퍼 관리를 최적화하고 메모리 할당 및 복사 작업을 줄입니다.

Leapcell: Golang 웹 애플리케이션을 위한 최고의 서버리스 플랫폼

Go Channel Unlocked: How They Work

마지막으로 Go 서비스 배포에 매우 적합한 플랫폼을 추천합니다: Leapcell

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

자세한 내용은 설명서를 확인하세요!

Leapcell 트위터: https://www.php.cn/link/7884effb9452a6d7a7a79499ef854afd

위 내용은 Go 채널 잠금 해제: 작동 방식의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
저자별 최신 기사
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿