Go 언어에서 Context의 역할과 사용법에 대한 자세한 설명

KDP(데이터 서비스 플랫폼)는 KaiwuDB가 독자적으로 개발한 데이터 서비스 제품으로 KaiwuDB를 핵심으로 산업용 사물 인터넷, 디지털 에너지, 차량 인터넷, 스마트 산업을 충족하는 AIoT 시나리오를 위한 원스톱 데이터 서비스 플랫폼입니다. 핵심 비즈니스 시나리오에서 데이터 수집, 처리, 계산, 분석 및 적용을 위한 포괄적인 비즈니스 요구 사항, "비즈니스는 데이터, 데이터는 서비스"를 실현하고 기업이 데이터에서 더 큰 비즈니스 가치를 발굴하도록 지원합니다.

데이터 서비스 플랫폼의 실시간 컴퓨팅 구성 요소를 개발할 때 실시간 컴퓨팅 구성 요소는 사용자에게 사용자 지정 규칙 기능을 제공하고 사용자가 여러 규칙을 등록하고 일정 기간 동안 실행한 후 이러한 문제에 직면할 수 있습니다. , 그런 다음 규칙의 정의를 수정하고 다시 시작하면 문제가 발생합니다.코 루틴 누출이 발생했습니다.

1. 실제 사례

이 글에서는 데이터 서비스 플랫폼의 실시간 컴퓨팅 구성 요소를 개발하는 과정에서 발생할 수 있는 코루틴 유출 문제를 의사 코드를 사용하여 종합적으로 소개합니다.

//规则的大致数据结构
type DummyRule struct{
    BaseRule    
    sorce []Source    
    sink  []Sink    
    //flow map key:flow 名称,value:flow 实例    
    flow map[string]Flow    
    ...
}

위의 DummyRule은 여러 데이터 소스 Source, 여러 데이터 대상 싱크 및 데이터 흐름 흐름을 포함하는 이 예제의 규칙 데이터 구조입니다. 규칙의 구체적인 프로세스는 다음과 같습니다.

1과 2는 두 개의 소스이며, 먼저 각각 1과 2의 두 소스를 더하여 처리하고, 두 번째로 Merge 작업을 호출하여 스트림을 합성한 다음 Fanout 작업을 수행하여 각각 7과 8로 흐르는 두 개의 동일한 스트림을 생성합니다. pass 7 및 8의 숫자 유형은 문자열로 변환되어 각각 out1.txt 및 out2.txt 파일에 기록됩니다.

type Source struct{
  consumers       []file.reader  
  out             chan interface{}  
  ctx             context.Context  
  cancel          context.CancelFunc  
  ...
}

위의 그림은 Source 클래스 데이터 소스의 의사 코드이며, Consumer는 파일 데이터를 읽는 데 사용되는 판독기이고, out은 다음 데이터 스트림으로 전달하는 데 사용되는 채널이며, ctx는 Go의 컨텍스트입니다. 소비자가 파일 데이터를 읽는 것은 별도의 코 루틴이며 읽은 데이터는 다음 데이터 스트림의 소비를 기다리면서 출력됩니다.

type Sink struct{
   producers  []file.writer   
   in         chan interface{}   
   ctx        context.Context   
   cancel context.CancelFunc   
   ...
}

위의 그림은 Sink 클래스 데이터 개체의 의사 코드이며, 생산자는 파일을 쓰는 데 사용되는 작성자이며, in은 이전 데이터 스트림을 수락하는 데 사용되는 채널이고, ctx는 Go의 컨텍스트이며, 생산자는 파일 데이터를 작성하는 것도 별도입니다. 코루틴 .

func(fm FlatMap) Via(flow streams.Flow) streams.Flow{
    go fm.transmit(flow)
    return flow
}

위의 그림은 데이터 흐름 전송의 소스 코드입니다. FlatMap의 사용법은 curFlow := prevFlow.Via(nextFlow)이므로 이전 Flow를 다음 Flow로 넘길 수 있고, 코루틴에서 데이터 흐름의 전달 과정이 진행되는 것을 볼 수 있습니다.

이전 소스 코드에서 이 예제 규칙에는 최소 10개의 코루틴이 있지만 실제로는 10개보다 훨씬 많은 코루틴이 있음을 알 수 있습니다. 데이터 서비스 플랫폼의 실시간 컴퓨팅 구성 요소에서 코루틴 관리가 매우 복잡하다는 것을 알 수 있습니다.

go pprof, top 및 go trace와 같은 도구를 사용하여 테스트 및 조사를 반복한 결과 규칙에서 Sink의 컨텍스트를 잘못 취소하여 코루틴 누출이 발생했음을 발견했습니다.

컨텍스트는 고루틴을 관리하기 위한 중요한 언어 기능입니다. 컨텍스트를 올바르게 사용하는 방법을 배우면 고루틴 간의 관계를 더 명확하게 하고 관리할 수 있습니다. 위의 예에서 우리는 Context의 중요성을 알 수 있습니다.Context를 올바르게 사용하는 방법을 배우면 코드 품질을 향상시킬 수 있을 뿐만 아니라 많은 코루틴 누출 조사 작업을 피할 수 있습니다.

둘, 맥락 속으로

1. 소개

컨텍스트는 일반적으로 컨텍스트라고 합니다. Go 언어에서는 고루틴의 실행 상태 및 장면으로 이해됩니다. 상위 고루틴과 하위 고루틴 간에 컨텍스트의 전송이 있으며 상위 고루틴은 컨텍스트를 하위 고루틴으로 전달합니다.

각 고루틴이 실행되기 전에 프로그램의 현재 실행 상태를 미리 알아야 하며, 일반적으로 이러한 상태는 Context 변수에 캡슐화되어 실행될 고루틴으로 전달됩니다.

네트워크 프로그래밍에서 네트워크 요청 Request를 수신하고 요청을 처리하면 여러 고루틴에서 처리될 수 있습니다. 그리고 이러한 고루틴은 요청의 일부 정보를 공유해야 할 수 있으며 요청이 취소되거나 시간이 초과되면 이 요청에서 생성된 모든 고루틴도 종료됩니다.

Go Context 패키지는 프로그램 단위 간에 상태 변수를 공유하는 방식을 구현할 뿐만 아니라 간단한 방법 단위로 호출된 프로그램 단위 외부의 ctx 변수 값을 설정하여 호출된 프로그램에 만료 또는 취소와 같은 신호를 전달할 수 있습니다.

네트워크 프로그래밍에서 A가 B의 API를 호출하고 B가 C의 API를 호출하면 A가 B를 호출하여 취소하면 B의 C 호출도 취소되어야 합니다. Context 패키지를 사용하면 요청 고루틴 간에 요청 데이터, 취소 신호 및 시간 초과 정보를 매우 편리하게 전달할 수 있습니다.

Context 패키지의 핵심은 Context 인터페이스입니다.

// A Context carries a deadline, a cancellation signal, and other values across
// API boundaries
//
// Context's methods may be called by multiple goroutines simultaneously.
type Context interface{     
     // 返回一个超时时间,到期则取消context。在代码中,可以通过deadline为io操作设置超过时间     
     Deadline() (deadline time.Time, ok bool)     
     // 返回一个channel,用于接收context的取消或者deadline信号。     
     // 当channel关闭,监听done信号的函数会立即放弃当前正在执行的操作并返回。     
     // 如果context实例时不可能取消的,那么     
     // 返回nil,比如空context,valueCtx     
     Done()
}

2. 사용방법

고루틴의 경우 생성 및 호출 관계는 항상 트리 구조와 같은 계층별 호출과 같으며 맨 위의 Context는 하위 고루틴의 실행을 적극적으로 닫을 수 있는 방법이 있어야 합니다. 이 관계를 실현하기 위해 Context도 트리 구조이며 리프 노드는 항상 루트 노드에서 파생됩니다.

Context 트리를 생성하기 위해서는 루트노드를 먼저 얻어야 하고 Context.Backupgroup 함수의 반환값은 루트노드이다.

func Background() Context{
    return background
}

이 함수는 일반적으로 요청을 받은 첫 번째 고루틴에 의해 생성되고 들어오는 요청에 해당하는 Context의 루트 노드인 빈 Context를 반환하며, 취소할 수 없고 값이 없으며 만료 시간이 없습니다. 그는 종종 요청을 처리하는 최상위 컨텍스트로 존재합니다.

루트 노드를 사용하여 하위 노드를 생성할 수 있습니다. Context 패키지는 하위 노드를 생성하는 일련의 방법을 제공합니다.

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {}
func WithDeadline(parent Context, d time.Time)(Context, CancelFunc) {}
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {}
func WithValue(parent Context, key, val interface{}) Context {}

이 함수는 Context 유형의 부모를 수신하고 Context 유형의 값을 반환하므로 계층별로 다른 Context가 생성되고 부모 노드를 복사하여 자식 노드를 얻고 자식 노드의 일부 상태 값은 받은 매개변수에 따라 설정한 다음 자식 노드를 하위 고루틴으로 전달할 수 있습니다.

컨텍스트를 통해 변경된 상태를 전달하는 방법은 무엇입니까?

부모 goroutine에서 Withxx 메서드를 통해 cancel 메서드를 얻을 수 있으므로 자식 Context를 조작할 권한을 얻습니다.

(1)취소와 함께

 WithCancel 함수는 부모 노드를 자식 노드로 복사하고 다음과 같이 정의되는 추가 CancelFunc 함수 유형 변수를 반환하는 것입니다. type CancelFunc func()

CancelFunc를 호출하면 해당 자식 Context 개체가 취소됩니다. 부모 고루틴에서 WithCancel을 통해 자식 노드의 Context를 생성할 수 있고, 자식 고루틴의 제어권도 획득할 수 있으며, CancelFunc 함수가 실행되면 자식 노드 Context는 종료된다. 종료 여부를 확인하고 goroutine을 종료합니다.

select {
case <- ctx.Cone():
    fmt.Println("do some clean work ...... ")
}

(2)마감시간

 WithDeadline의 기능은 WithCancel의 기능과 유사하며 부모 노드를 자식 노드로 복사하기도 하지만 만료 시간은 Deadline과 부모의 만료 시간에 의해 결정됩니다. 부모의 만료 시간이 기한보다 빠른 경우 반환되는 만료 시간은 부모의 만료 시간과 동일합니다. 상위 노드가 만료되면 모든 하위 노드를 동시에 닫아야 합니다.

(3) 제한 시간 초과

WithTimeout 함수는 지금부터 Context의 남은 수명을 전달한다는 점을 제외하면 WithDeadline과 유사합니다. 또한 둘 다 CancelFunc 유형의 함수 변수인 생성된 자식 컨텍스트의 제어를 반환합니다.

최상위 Request 요청 기능이 종료되면 특정 Context를 취소할 수 있으며 자손 고루틴은 select ctx.Done()에 따라 종료를 판단합니다.

(4)가치 있는

WithValue 함수는 부모의 복사본을 반환하고 이 복사본의 Value(key) 메서드를 호출하면 값을 가져옵니다. 이렇게 하면 루트 노드의 원래 값을 유지할 뿐만 아니라 하위 노드에도 새 값을 추가하므로 동일한 키가 있으면 덮어쓰게 됩니다.

3. 예시

package main
import (
        "context"        
        "fmt"        
        "time"
)
func main() {
        ctxWithCancel, cancel := context.WithTimeout(context.Background(), 5 * time.Second)                
        
        go worker(ctxWithCancel, "[1]")        
        go worker(ctxWithCancel, "[2]")                
        
        go manager(cancel)                
        
        <-ctxWithCancel.Done()        
        // 暂停1秒便于协程的打印输出        
        time.Sleep(1 * time.Second)        
        fmt.Println("example closed")
}
func manager(cancel func( )) {
        time.Sleep(10 * time.Second)         
        fmt.Println("manager called cancel()")         
        cancel() 
}                
func worker(ctxWithCancle context.Context, name string) {
        for {
                 select {                 
                 case <- ctxWithCancel.Done():                          
                          fmt.Println(name, "return for ctxWithCancel.Done()")                          
                          return                 
                 default:
                          fmt.Println(name, "working")                 
                          }                 
                          time.Sleep(1 * time.Second)        
        }
}

이 프로세스 컨텍스트의 아키텍처 다이어그램:

[1]working
[2]working
[2]working
[1]working
[1]working
[2]working
[2]working
[1]working
[1]working
[2]working
[1]return for ctxWithCancel.Done()
[2]return for ctxWithCancel.Done()example closed

이번에 작업자가 종료된 것은 ctxWithCancel의 타이머 만료로 인한 것임을 알 수 있습니다.

관리자의 지속 시간을 2초로 변경하고 WithTimeout의 지속 시간을 변경하지 않고 다시 실행하면 작업자는 2초 동안만 작업한 후 관리자에게 미리 중지되었습니다.

[1]working
[2]working
[2]working
[1]workingmanager called cancel()
[1]return for ctxWithCancel.Done()
[2]return for ctxWithCancel.Done()example closed

추천

출처blog.csdn.net/ZNBase/article/details/131410274