Go 어프어간트 소개

ants는 대규모 고루틴의 스케줄링과 관리를 구현하여 리소스의 한계와 재사용을 가능하게 함으로써 동시 프로그램을 개발할 때 작업을 효율적으로 실행할 수 있도록 하는 고성능 고루틴 풀입니다.

특징

  • 대규모 고루틴을 자동으로 스케줄링하고 재사용합니다.
  • 만료된 고루틴을 정기적으로 정리하여 추가로 리소스를 절약합니다.
  • 작업 제출, 실행 중인 고루틴 수 얻기, 풀 크기 동적 조정, 풀 해제, 풀 재시작과 같은 많은 유용한 인터페이스를 제공합니다.
  • 프로그램 충돌을 방지하기 위해 패닉을 정상적으로 처리합니다.
  • 리소스 재사용은 메모리 사용량을 크게 줄입니다. 대규모 일괄 동시 작업 시 기본 고루틴 동시성보다 높은 성능을 보여줍니다.
  • 비차단 메커니즘

ants 작동 방식

플로우 차트

ants-flowchart-cn

애니메이션 이미지들

설치

ants v1 버전 사용:

go get -u github.com/panjf2000/ants

ants v2 버전 사용 (GO111MODULE=on 활성화):

go get -u github.com/panjf2000/ants/v2

사용법

대량의 고루틴을 실행하는 Go 동시 프로그램을 작성할 때 시스템 리소스(메모리, CPU)를 상당량 소비하는데, ants를 사용하면 고루틴을 재사용하는 고루틴 풀을 생성하여 리소스를 절약하고 성능을 향상시킬 수 있습니다.

package main

import (
	"fmt"
	"sync"
	"sync/atomic"
	"time"

	"github.com/panjf2000/ants/v2"
)

var sum int32

func myFunc(i interface{}) {
	n := i.(int32)
	atomic.AddInt32(&sum, n)
	fmt.Printf("run with %d\n", n)
}

func demoFunc() {
	time.Sleep(10 * time.Millisecond)
	fmt.Println("Hello World!")
}

func main() {
	defer ants.Release()

	runTimes := 1000

	// 공통 풀 사용.
	var wg sync.WaitGroup
	syncCalculateSum := func() {
		demoFunc()
		wg.Done()
	}
	for i := 0; i < runTimes; i++ {
		wg.Add(1)
		_ = ants.Submit(syncCalculateSum)
	}
	wg.Wait()
	fmt.Printf("실행 중인 고루틴 수: %d\n", ants.Running())
	fmt.Printf("모든 작업 완료.\n")

	// 함수가 있는 풀 사용,
	// 고루틴 풀의 용량을 10으로, 만료 기간을 1초로 설정.
	p, _ := ants.NewPoolWithFunc(10, func(i interface{}) {
		myFunc(i)
		wg.Done()
	})
	defer p.Release()
	// 작업을 하나씩 제출.
	for i := 0; i < runTimes; i++ {
		wg.Add(1)
		_ = p.Invoke(int32(i))
	}
	wg.Wait()
	fmt.Printf("실행 중인 고루틴 수: %d\n", p.Running())
	fmt.Printf("모든 작업 완료, 결과는 %d\n", sum)
}

풀 구성

// Option은 선택적인 함수를 나타냅니다.
type Option func(opts *Options)

// Options는 ants 풀을 인스턴스화할 때 적용될 모든 옵션을 포함합니다.
type Options struct {
	// ExpiryDuration은 청소용 고루틴이 만료된 워커를 정리하는 기간입니다.
	// 청소용 고루틴은 모든 `ExpiryDuration`마다 모든 워커를 스캔하고
	// `ExpiryDuration` 이상 사용되지 않은 워커를 정리합니다.
	ExpiryDuration time.Duration

	// PreAlloc은 Pool을 초기화할 때 메모리를 사전 할당할지 여부를 나타냅니다.
	PreAlloc bool

	// pool.Submit에서 블로킹되는 고루틴의 최대 수입니다.
	// 0(기본값)은 해당 제한이 없음을 의미합니다.
	MaxBlockingTasks int

	// Nonblocking이 true인 경우 Pool.Submit은 절대로 블로킹되지 않습니다.
	// Pool.Submit이 한 번에 처리할 수 없는 경우 ErrPoolOverload가 반환됩니다.
	// Nonblocking이 true인 경우 MaxBlockingTasks는 작동하지 않습니다.
	Nonblocking bool

	// PanicHandler는 각 워커 고루틴에서의 패닉을 처리하는 데 사용됩니다.
	// nil인 경우 패닉은 다시 워커 고루틴에서 발생합니다.
	PanicHandler func(interface{})

	// Logger는 정보를 기록하는 사용자 정의 로거입니다. 설정되지 않은 경우
	// log 패키지의 기본 표준 로거가 사용됩니다.
	Logger Logger
}

// WithOptions는 전체 옵션 구성을 허용합니다.
func WithOptions(options Options) Option {
	return func(opts *Options) {
		*opts = options
	}
}

// WithExpiryDuration은 고루틴 정리 간격 시간을 설정합니다.
func WithExpiryDuration(expiryDuration time.Duration) Option {
	return func(opts *Options) {
		opts.ExpiryDuration = expiryDuration
	}
}

// WithPreAlloc은 워커를 위해 메모리를 사전 할당해야 하는지를 나타냅니다.
func WithPreAlloc(preAlloc bool) Option {
	return func(opts *Options) {
		opts.PreAlloc = preAlloc
	}
}

// WithMaxBlockingTasks는 풀 용량에 도달하면 블로킹되는 고루틴의 최대 수를 설정합니다.
func WithMaxBlockingTasks(maxBlockingTasks int) Option {
	return func(opts *Options) {
		opts.MaxBlockingTasks = maxBlockingTasks
	}
}

// WithNonblocking은 사용 가능한 워커가 없을 때 풀이 nil을 반환해야 하는지를 나타냅니다.
func WithNonblocking(nonblocking bool) Option {
	return func(opts *Options) {
		opts.Nonblocking = nonblocking
	}
}

// WithPanicHandler는 패닉 핸들러를 설정합니다.
func WithPanicHandler(panicHandler func(interface{})) Option {
	return func(opts *Options) {
		opts.PanicHandler = panicHandler
	}
}

// WithLogger는 사용자 정의 로거를 설정합니다.
func WithLogger(logger Logger) Option {
	return func(opts *Options) {
		opts.Logger = logger
	}
}

NewPool/NewPoolWithFunc를 호출할 때 다양한 선택적 함수를 사용하여 ants.Options의 각 구성 항목의 값을 설정할 수 있으며, 이후 고루틴 풀을 사용자 정의할 수 있습니다.

사용자 정의 풀

특정 풀 용량으로 사용자 고유의 Pool을 인스턴스화하는 데 ants를 지원합니다. 다음과 같이 NewPool 메서드를 호출하여 지정된 용량으로 새로운 Pool을 인스턴스화할 수 있습니다.

p, _ := ants.NewPool(10000)

작업 제출

작업은 ants.Submit(func()) 메서드를 호출하여 제출됩니다.

ants.Submit(func(){})

고루틴 풀 용량 동적 조정

고루틴 풀 용량을 동적으로 조정하려면 Tune(int) 메서드를 사용할 수 있습니다.

pool.Tune(1000) // 용량을 1000으로 조정
pool.Tune(100000) // 용량을 100000으로 조정

이 방법은 스레드로부터 안전합니다.

고루틴 큐 메모리 사전 할당

ants를 사용하면 전체 풀에 메모리를 사전 할당할 수 있으며, 이는 특정 시나리오에서 고루틴 풀의 성능을 향상시킬 수 있습니다. 예를 들어, 매우 큰 용량이 필요하고 각 고루틴 내의 작업이 시간이 오래 걸리는 시나리오에서는 고루틴 큐에 대한 메모리 사전 할당이 불필요한 메모리 재할당을 줄일 수 있습니다.

// 이 함수를 호출하면 ants는 풀의 전체 용량을 미리 할당합니다.
p, _ := ants.NewPool(100000, ants.WithPreAlloc(true))

풀 해제

pool.Release()

풀 재시작

// Reboot() 메서드를 호출하여 이전에 파괴된 풀을 다시 활성화하고 다시 사용할 수 있습니다.
pool.Reboot()

작업 실행 순서에 관하여

ants는 작업 실행 순서를 보장하지 않으며, 실행 순서는 제출 순서와 일치하지 않을 수 있습니다. 이는 ants가 모든 제출된 작업을 동시에 처리하고, 작업이 동시에 실행 중인 워커에 할당되어 작업이 동시에 실행되어 결정론적이지 않은 순서로 실행되기 때문입니다.