소개
Go 1.23 버전부터 새롭게 등장한 iter
패키지는 개발자들에게 반복 처리(iteration)를 보다 효율적이고 유연하게 관리할 수 있는 방법을 제공합니다.
iter
패키지는 추상화된 반복자(iterator) 개념을 도입하여, 기존의 반복 처리 방식을 개선하고 다양한 활용 가능성을 열어줍니다.
이 글에서는 iter
패키지의 기본 개념과 사용법, 그리고 실제 활용 예시를 통해 Go 개발에서의 활용성을 살펴보겠습니다.
iter 패키지의 핵심 개념
iter
패키지는 주로 for-range
문과 함께 사용됩니다.
핵심적인 역할은 컨텍스트(context)를 가진 논리적인 열거 가능 객체를 생성하고, 이를 다른 코드 블록에서 for-range
를 통해 순회하는 데 도움을 주는 것입니다.
기존에는 goroutine과 channel을 활용하여 범위를 분할하는 방식으로 이러한 기능을 구현했습니다.
하지만 이 방식은 for-range
루프를 중간에 중단해야 할 경우 goroutine이 남아 처리되지 않는 문제점을 가지고 있었습니다.
또한, 단순히 반복 처리를 위해 goroutine과 channel을 사용하는 것은 자원 낭비가 될 수 있습니다.
기존 방식의 한계점: goroutine과 channel을 이용한 반복 처리
다음은 goroutine과 channel을 사용하여 슬라이스를 순회하는 예시입니다.
package main
func iter1[T any](a []T) func() (T, bool) {
ch := make(chan T)
go func() {
defer close(ch)
for _, v := range a {
ch <- v
}
}()
return func() (T, bool) {
v, ok := <-ch
return v, ok
}
}
func main() {
vv := iter1([]int{1, 2, 3})
for {
v, ok := vv()
if !ok {
break
}
println(v)
}
}
위 코드에서 for
루프를 중간에 break
하더라도, goroutine
은 계속 실행되어 자원을 소모합니다.
이러한 문제를 해결하기 위해 context
패키지를 활용하여 goroutine
을 제어할 수 있지만, 코드가 더욱 복잡해집니다.
context를 활용한 개선
context
를 활용하면 goroutine
의 실행을 제어하여 자원 낭비를 줄일 수 있습니다.
package main
import (
"context"
)
func iter1[T any](ctx context.Context, a []T) func() (T, bool) {
ch := make(chan T)
go func() {
defer close(ch)
for _, v := range a {
select {
case ch <- v:
case <-ctx.Done():
}
}
}()
return func() (T, bool) {
v, ok := <-ch
return v, ok
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
vv := iter1(ctx, []int{1, 2, 3})
for {
v, ok := vv()
if !ok {
break
}
println(v)
if v == 2 {
cancel()
break
}
}
}
하지만 이러한 방식은 반복 처리를 위해 불필요한 goroutine
과 channel
을 생성해야 한다는 단점을 여전히 가지고 있습니다.
iter 패키지 활용
iter
패키지는 이러한 문제점을 해결하고 보다 효율적인 반복 처리를 가능하게 합니다.
iter
패키지는 Seq
와 Seq2
라는 두 가지 주요 타입을 제공합니다.
Seq[V any]
: 슬라이스와 같이 단일 값을 반복하는 경우 사용합니다.Seq2[K, V any]
: 맵과 같이 키-값 쌍을 반복하는 경우 사용합니다.
iter.Seq와 iter.Seq2의 차이
iter.Seq
는 슬라이스를 for-range
로 처리하는 방식과 유사합니다.
a := []int{1, 2, 3}
for v := range a {
// v를 사용하는 코드
}
반면 iter.Seq2
는 맵을 for-range
로 처리하는 방식과 유사합니다.
m := map[string]int{}
for k, v := range m {
// k 또는 v를 사용하는 코드
}
iter.Seq를 이용한 반복 처리 예시
iter.Seq
를 사용하여 모든 값을 출력하는 함수를 다음과 같이 정의할 수 있습니다.
func PrintAll[V any](seq iter.Seq[V]) {
for v := range seq {
fmt.Println(v)
}
}
위 함수는 기존의 for-range
문과 동일한 방식으로 iter.Seq
를 사용합니다.
iter 구현: 파일에서 줄 읽어오기
iter
패키지를 직접 구현하는 방법은 조금 복잡하지만, 일관된 패턴을 따르면 어렵지 않습니다.
예를 들어, 파일에서 줄 단위로 데이터를 읽어와 for-range
로 처리하는 함수를 구현해보겠습니다.
package main
import (
"bufio"
"io"
"iter"
"log"
"os"
)
func lines(r io.Reader) iter.Seq[string] {
scanner := bufio.NewScanner(r)
return func(yield func(string) bool) {
for scanner.Scan() {
if !yield(scanner.Text()) {
break
}
}
}
}
lines
함수는 io.Reader
를 입력받아 iter.Seq[string]
을 반환합니다.
iter.Seq
의 본체는 함수이며, 이 함수의 인수 yield
는 각 줄을 처리하는 코루틴 역할을 합니다.
yield
함수는 처리할 값을 입력받고, 반환값 bool
은 for-range
루프를 계속 진행할지 여부를 결정합니다.
false
를 반환하면 for-range
루프가 중단됩니다.
iter.Seq2를 이용한 반복 처리 예시
iter.Seq2
를 사용하는 예시로, 학급의 학생 성적을 관리하는 구조체를 생각해 볼 수 있습니다.
type ClassRoom struct {
// ...
}
func (cr *ClassRoom) Scores() iter.Seq2[string, int] {
// ...
}
func main() {
cr, _ := loadClassRoom("3A")
for name, score := range cr.Scores() {
fmt.Printf("name=%v, score=%v\n", name, score)
}
}
표준 패키지에서의 iter 활용
slices
와 maps
패키지에는 iter
패키지와 연동하여 사용할 수 있는 다양한 함수들이 추가되었습니다.
예를 들어, slices.Values
함수는 슬라이스의 모든 값을 iter.Seq
로 반환하고, maps.Keys
함수는 맵의 모든 키를 iter.Seq
로 반환합니다.
iter의 효과: 메모리 효율성
iter
패키지는 단순히 범위를 분리하는 것뿐만 아니라 메모리 효율성도 향상시킵니다.
예를 들어, 매우 큰 문자열을 공백으로 분리하여 처리해야 하는 경우, strings.Split
함수를 사용하면 모든 토큰을 메모리에 저장해야 하므로 메모리 부족 현상이 발생할 수 있습니다.
하지만 iter
패키지를 사용하면 필요한 토큰만 메모리에 로드하여 처리할 수 있으므로 메모리 사용량을 줄일 수 있습니다.
벤치마크 결과
다음은 strings.Split
함수와 iter.Seq
를 사용한 벤치마크 결과입니다.
goos: linux
goarch: amd64
pkg: iterbench
cpu: Intel(R) Core(TM) i5-8250U CPU @ 1.60GHz
BenchmarkWithoutIter-8 190472 5547 ns/op 1792 B/op 1 allocs/op
BenchmarkWithIter-8 492627 2129 ns/op 24 B/op 2 allocs/op
PASS
ok iterbench 2.743s
결과에서 확인할 수 있듯이, iter
패키지를 사용하면 실행 속도와 메모리 사용량을 모두 개선할 수 있습니다.
마무리
Go 1.23 버전부터 도입된 iter
패키지는 반복 처리를 보다 효율적이고 유연하게 관리할 수 있는 강력한 도구입니다.
처음에는 함수 시그니처가 다소 복잡해 보일 수 있지만, 익숙해지면 iter
패키지를 활용하여 코드를 더욱 깔끔하고 효율적으로 작성할 수 있습니다.
iter
패키지를 적극 활용하여 견고하고 유지보수하기 쉬운 Go 애플리케이션을 개발해 보시기 바랍니다.
'Go' 카테고리의 다른 글
AriaSQL: Go 언어로 만든 새로운 관계형 데이터베이스의 탄생 (2) | 2024.09.07 |
---|---|
Argon/Bcrypt가 CPU를 100% 사용하는 이유와 해결책 (2) | 2024.09.07 |
Go 1.23 이터레이터 완벽 정리 (0) | 2024.08.20 |
Go 1.22의 새로운 기능: cmp.Or (0) | 2024.07.29 |
Go 1.22의 새로운 기능: slices.Concat (0) | 2024.07.29 |