>백엔드 개발 >Golang >golang 함수 포인터가 잘못되었습니다.

golang 함수 포인터가 잘못되었습니다.

王林
王林원래의
2023-05-10 17:11:37594검색

golang에서 함수 포인터는 중요한 유형입니다. 주요 용도에는 콜백 함수, 라이브러리 함수의 동적 로딩 등이 있습니다. 따라서 함수 포인터의 실패는 프로그램의 정확성에 심각한 영향을 미칩니다.

그러나 실제 개발 환경에서는 함수 포인터가 유효하지 않게 되어 프로그램에 이상한 오류가 발생하는 상황이 종종 발생합니다. 이 기사에서는 함수 포인터 오류의 원인을 분석하고 이러한 상황을 방지하는 방법을 논의하기 위해 특정 사례를 예로 들어 설명합니다.

사례 분석

GraphQL API 서비스에서는 타사 라이브러리를 사용하여 GraphQL 쿼리 문을 구문 분석하고 사용자 지정 함수를 사용하여 일부 필드를 처리했습니다. 이 라이브러리는 함수 포인터 유형을 제공하므로 사용자 정의 함수를 여기에 전달하기만 하면 됩니다.

구체적으로 코드 조각은 다음과 같습니다.

type fieldResolver func(ctx context.Context, obj interface{}, args map[string]interface{}) (interface{}, error)
type Resolver struct {
    // ...
    fieldResolvers map[string]fieldResolver
    // ...
}

func (r *Resolver) AddFieldResolver(fieldName string, fr fieldResolver) {
    r.fieldResolvers[fieldName] = fr
}

AddFieldResolver 메서드는 Resolver 유형 구조에 필드 해결 함수 포인터를 추가하는 데 사용됩니다. 동시에 이 Resolver 유형 구조는 GraphQL Resolver 인터페이스도 구현합니다.

제 구현에서는 이 Resolver 유형 구조에 두 개의 필드 확인 함수 포인터를 추가했습니다. 이 두 함수 포인터는 각각 name과 Created_at라는 두 필드 이름으로 결정됩니다.

func (r *Resolver) BaseQuery() QueryResolver {
    return &queryResolver{r}
}
func (r *Resolver) name(ctx context.Context, obj interface{}, f graphql.ResolveFieldParams) (interface{}, error) {
    // handler for name field
}

func (r *Resolver) created_at(ctx context.Context, obj interface{}, f graphql.ResolveFieldParams) (interface{}, error) {
    // handler for created_at field
}

func (r *Resolver) initFieldResolvers() {
    r.fieldResolvers = map[string]fieldResolver{
        "name":          r.name,
        "created_at":    r.created_at,
    }
}

이 두 필드 확인 함수 포인터의 초기화는 Resolver 유형 구조의 생성자에서 호출되는 initFieldResolvers 메서드에서 완료됩니다.

또한 Resolver 유형 구조에 특정 Resolver 인터페이스를 구현해야 합니다. 구체적인 방법은 다음과 같습니다.

type queryResolver struct{ *Resolver }

func (r *queryResolver) Name(ctx context.Context, obj *types.User) (string, error) {
    resolver, ok := r.fieldResolvers["name"]
    if !ok {
        return "", fmt.Errorf("resolver not found")
    }
    result, err := resolver(ctx, obj, nil)
    if err != nil {
        return "", err
    }
    return result.(string), nil
}

func (r *queryResolver) CreatedAt(ctx context.Context, obj *types.User) (string, error) {
    resolver, ok := r.fieldResolvers["created_at"]
    if !ok {
        return "", fmt.Errorf("resolver not found")
    }
    result, err := resolver(ctx, obj, nil)
    if err != nil {
        return "", err
    }
    return result.(string), nil
}

이것은 이전에 등록한 구문 분석 함수 포인터, 즉 두 함수 name과 Created_at를 동적으로 호출합니다.

그러나 테스트 과정에서 이 구현이 매우 불안정하다는 것을 발견했습니다. 가끔은 정상적으로 작동할 수도 있었지만, 어떤 때는 "리졸버를 찾을 수 없습니다"라는 오류가 보고되었습니다.

원인 분석

이 상황에 대해 먼저 함수 포인터가 무효화될 가능성을 생각했습니다. 실제 개발에서 우리는 종종 유사한 문제에 직면합니다. 함수 포인터를 구조 변수에 저장한 다음 이 변수의 값을 다른 곳에서 호출하면 포인터가 만료되었음을 알게 됩니다.

Go 언어에서도 다른 언어와 마찬가지로 함수는 일급 시민이며 함수 포인터도 변수로 저장됩니다. 정상적인 상황에서는 함수 포인터가 만료되지 않습니다. 이론적으로 메모리에 저장되는 것은 호출할 수 있는 포인터 주소이며, 포인터는 프로그램이 끝날 때까지 재활용되지 않습니다.

그러나 우리 시나리오에서는 이 Resolver 유형 구조가 여러 코루틴에서 공유되므로 동시 액세스가 발생하고 메모리를 해제하기 위해 코루틴이 종료되는 상황도 있습니다. 이로 인해 함수 포인터가 유효하지 않게 될 수 있습니다.

Solution

함수 포인터 무효화 문제를 해결하는 것은 본질적으로 포인터 무효화를 방지하는 것입니다. golang의 동시 프로그래밍에는 동시에 액세스할 때 특정 데이터가 잘못되지 않도록 보장하는 몇 가지 기술적 수단이 있습니다. 다음으로 잘못된 함수 포인터를 방지하기 위한 두 가지 일반적인 기술을 소개하겠습니다.

  1. 구조를 복사하여 데이터 경쟁을 피하세요

위 코드의 Resolver 유형 구조의 경우 sync.RWMutex 유형을 사용하여 fieldResolvers 필드의 동시 읽기 및 쓰기를 보호할 수 있습니다. 이렇게 하면 fieldResolvers 필드를 읽을 때 경쟁 조건이 발생하지 않도록 할 수 있습니다.

동시에 맵 유형 대신 함수 포인터 조각을 사용할 수도 있습니다. 함수 포인터를 읽을 때 맵 유형에 액세스할 필요가 없으므로 경쟁 조건이 발생하지 않습니다.

구체적인 코드는 다음과 같습니다.

type Resolver struct {
    sync.RWMutex
    fieldResolvers []*fieldResolver
}

func (r *Resolver) AddFieldResolver(fr *fieldResolver) {
    r.Lock()
    defer r.Unlock()

    r.fieldResolvers = append(r.fieldResolvers, fr)
}

func (r *Resolver) name(ctx context.Context, obj interface{}, f graphql.ResolveFieldParams) (interface{}, error) {
    // handler for name field
}

func (r *Resolver) created_at(ctx context.Context, obj interface{}, f graphql.ResolveFieldParams) (interface{}, error) {
    // handler for created_at field
}

func (r *Resolver) initFieldResolvers() {
    r.AddFieldResolver(&r.name)
    r.AddFieldResolver(&r.created_at)
}

여기서는 fieldResolver의 종류를 map[string]fieldResolver에서 []*fieldResolver로 변경했습니다. 포인터 유형을 사용하면 중복 메모리 할당 및 데이터 복사가 방지됩니다.

  1. 채널에 함수 포인터 저장

잘못된 함수 포인터를 방지하는 또 다른 방법은 함수 포인터를 채널에 저장하는 것입니다. 특히 함수 포인터를 호출해야 하는 경우 함수 포인터를 채널로 보내고 호출하기 전에 채널이 함수 포인터를 반환할 때까지 다른 코루틴에서 기다릴 수 있습니다.

이러한 방식으로 함수 포인터가 만료되지 않도록 보장할 수 있으며 코루틴 종료 시 메모리 해제와 같은 문제를 피할 수 있습니다.

구체적인 코드는 다음과 같습니다.

type Resolver struct {
    fieldResolvers chan *fieldResolver
    // ...
}

func (r *Resolver) AddFieldResolver(fr *fieldResolver) {
    r.fieldResolvers <- fr
}

func (r *Resolver) initFieldResolvers() {
    // ...
    go func() {
        for fr := range r.fieldResolvers {
            if fr != nil {
                // call the function pointer
            }
        }
    }()
}

여기서 fieldResolvers의 유형을 chan *fieldResolver로 변경하고 코루틴을 통해 이 채널의 함수 포인터를 호출했습니다.

결론

golang에서 발생하는 함수 포인터 무효화 문제에 대해서는 프로그램의 동시성 및 메모리 해제 문제에 주의할 필요가 있습니다. 경쟁 조건과 메모리 관리 문제를 피하기 위해 RWMutex 및 chan과 같은 golang의 강력한 동시 프로그래밍 기능을 활용할 수 있습니다.

동시에 포인터 유형의 사용에도 주의를 기울여야 하며 불필요한 메모리 할당 및 데이터 복사를 피하여 함수 포인터 실패 확률을 최대한 줄여야 합니다.

위 내용은 golang 함수 포인터가 잘못되었습니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.