Go ๊ธฐ์ ๋ฉด์ : Goroutine, Channel, ๋์์ฑ ํจํด ์๋ฒฝ ๊ฐ์ด๋
Go ๊ธฐ์ ๋ฉด์ ์์ ์์ฃผ ์ถ์ ๋๋ goroutine, channel, ๋์์ฑ ๊ด๋ จ ์ง๋ฌธ์ ๋ค๋ฃน๋๋ค. ํ๋ก๋์ ์์ค์ ์ฝ๋ ์์ ์ ๊ฐ ๋ต๋ณ์ ์ค๊ณ ๊ทผ๊ฑฐ๋ฅผ 2026๋ ๋ฉด์ ๋๋น์ฉ์ผ๋ก ์์ธํ ์ค๋ช ํฉ๋๋ค.

Go ๊ธฐ์ ๋ฉด์ ์์ goroutine, channel, ๋์์ฑ์ ๊ดํ ์ง๋ฌธ์ ๊ฐ์ฅ ๋์ด๋๊ฐ ๋์ ์ฃผ์ ๋ก ๊ผฝํ๋๋ค. ์ด๋ฌํ ๊ฐ๋ ์ ๋ํ ๊น์ ์ดํด๊ฐ ์๋์ด Go ์์ง๋์ด์ ํ์ต ๋จ๊ณ์ ๊ฐ๋ฐ์๋ฅผ ๊ตฌ๋ถํ๋ ํต์ฌ ์์์ ๋๋ค. ์ด ๊ฐ์ด๋์์๋ 2026๋ ๋ฉด์ ์์ ์ค์ ๋ก ์ถ์ ๋๋ ์ง๋ฌธ์ ํ๋ก๋์ ๊ธ ์ฝ๋ ์์ ๋ฐ ๋ต๋ณ์ ๊ทผ๊ฑฐ์ ํจ๊ป ์ค๋ช ํฉ๋๋ค.
Go ๋์์ฑ ๋ฉด์ ์ ์ธ ๊ฐ์ง ์์ญ์ ์ง์ค๋ฉ๋๋ค: goroutine ๋ผ์ดํ์ฌ์ดํด ๊ด๋ฆฌ, channel ์๋งจํฑ์ค(๋ฒํผ ์๋/์๋, ๋ฐฉํฅ ํ์ ), ํจํด ์กฐํฉ(fan-out/fan-in, ์์ปค ํ, context ์ทจ์). ๋ฌธ๋ฒ ์๊ธฐ๋ง์ผ๋ก๋ ๋ถ์กฑํ๋ฉฐ, ๋ ์ด์ค ์ปจ๋์ ๊ณผ ๋ฐ๋๋ฝ์ ๋ํ ๋ ผ๋ฆฌ์ ์ถ๋ก ๋ฅ๋ ฅ์ด ์๊ตฌ๋ฉ๋๋ค.
Goroutine ๊ธฐ๋ณธ ๊ฐ๋ : ๋ฉด์ ํ์ ์ง๋ฌธ
๋ฉด์ ์ ์ฒซ ๋จ๊ณ์์๋ ํ๋ณด์๊ฐ goroutine์ ๋ณธ์ง์ ์ดํดํ๊ณ ์๋์ง ํ์ธํฉ๋๋ค. ๋จ์ํ goroutine์ ์์ฑํ๋ ๋ฐฉ๋ฒ์ ์๋ ๊ฒ๋ง์ผ๋ก๋ ์ถฉ๋ถํ์ง ์์ต๋๋ค.
Q: goroutine์ด๋ ๋ฌด์์ด๋ฉฐ, OS ์ค๋ ๋์ ์ด๋ป๊ฒ ๋ค๋ฆ ๋๊น?
goroutine์ ์ด์์ฒด์ ๊ฐ ์๋ Go ๋ฐํ์ ์ค์ผ์ค๋ฌ์ ์ํด ๊ด๋ฆฌ๋๋ ๊ฒฝ๋ ๋์ ์คํ ํจ์์ ๋๋ค. Go ๋ฐํ์์ M:N ์ค์ผ์ค๋ง ๋ชจ๋ธ(M๊ฐ์ goroutine์ N๊ฐ์ OS ์ค๋ ๋์ ๋งคํ)์ ์ฌ์ฉํ์ฌ ์์ฒ ๊ฐ์ goroutine์ ์์์ OS ์ค๋ ๋์ ๋ค์คํํฉ๋๋ค.
package main
import (
"fmt"
"runtime"
"sync"
)
func main() {
// Print the number of OS threads available
fmt.Println("GOMAXPROCS:", runtime.GOMAXPROCS(0))
var wg sync.WaitGroup
for i := 0; i < 10000; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
// Each goroutine starts with ~2-8KB stack
// OS threads typically start with 1-8MB
_ = id
}(i)
}
wg.Wait()
fmt.Println("All 10,000 goroutines completed")
}๋ฉด์ ์์ ๋ฐ๋์ ์ธ๊ธํด์ผ ํ ์ฃผ์ ์ฐจ์ด์ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค. goroutine์ ๋์ ์ผ๋ก ํ์ฅ๋๋ 2-8KB ์คํ์ผ๋ก ์์๋๋ ๋ฐ๋ฉด, OS ์ค๋ ๋๋ ๊ณ ์ ๋ 1-8MB ์คํ์ ๊ฐ์ง๋๋ค. goroutine ๊ฐ์ ์ปจํ ์คํธ ์ค์์นญ์ Go ์ค์ผ์ค๋ฌ์ ์ํด ์ ์ ๊ณต๊ฐ์์ ์ฒ๋ฆฌ๋๋ฏ๋ก, OS ์ค๋ ๋์ ์ปค๋ ๋ ๋ฒจ ์ปจํ ์คํธ ์ค์์นญ์ ๋ฐ๋ฅธ ๋น์ฉ์ ํผํ ์ ์์ต๋๋ค. ๋ฐ๋ผ์ 10๋ง ๊ฐ์ goroutine์ ์์ฑํ๋ ๊ฒ์ ํ์ค์ ์ด์ง๋ง, 10๋ง ๊ฐ์ OS ์ค๋ ๋๋ ์์คํ ๋ฆฌ์์ค๋ฅผ ๊ณ ๊ฐ์ํต๋๋ค.
Q: goroutine์์ panic์ด ๋ฐ์ํ๋ฉด ์ด๋ป๊ฒ ๋ฉ๋๊น?
goroutine ๋ด์์ ๋ณต๊ตฌ๋์ง ์์ panic์ ํ๋ก๊ทธ๋จ ์ ์ฒด๋ฅผ ํฌ๋์์ํต๋๋ค. Java๋ Python์ ์์ธ์ ๋ฌ๋ฆฌ, panic์ ํด๋น goroutine ์์ฒด์ ์ฝ ์คํ๋ง์ ๋ฐ๋ผ ์ ํ๋ฉ๋๋ค(์์ฑํ goroutine์ ์คํ์ด ์๋๋๋ค). ์ด๋ฅผ ์ก๋ ์ ์ผํ ๋ฐฉ๋ฒ์ ๋์ผ goroutine ๋ด์ defer ํจ์์์ recover()๋ฅผ ํธ์ถํ๋ ๊ฒ์
๋๋ค.
package main
import "fmt"
func safeGo(fn func()) {
go func() {
defer func() {
if r := recover(); r != nil {
fmt.Println("recovered from panic:", r)
}
}()
fn() // execute the actual work
}()
}
func main() {
safeGo(func() {
panic("something went wrong")
})
// Give goroutine time to complete
select {}
}๋ฉด์ ๊ด์ ํ๋ก๋์
Go ์๋น์ค์์ goroutine ์คํ์ ๋ณต๊ตฌ ํจํด์ผ๋ก ๊ฐ์ธ๋ ๊ดํ์ ๋ํ ์ธ์์ ํ์ธํฉ๋๋ค. errgroup ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ฉด ๋ ์ธ๋ จ๋ ์ฒ๋ฆฌ๊ฐ ๊ฐ๋ฅํฉ๋๋ค.
Channel ์๋งจํฑ์ค: ๋ฒํผ ์๋/์๋ ์ฑ๋๊ณผ ๋ฐฉํฅ ํ์
channel ๊ด๋ จ ์ง๋ฌธ์ ํ๋ณด์๊ฐ Go์ ๋์์ฑ ๋ชจ๋ธ์ ์ง์ ์ผ๋ก ์ดํดํ๊ณ ์๋์ง, ์๋๋ฉด ํจํด๋ง ์๊ธฐํ ๊ฒ์ธ์ง๋ฅผ ํ๋ณํฉ๋๋ค.
Q: ๋ฒํผ ์๋ channel๊ณผ ๋ฒํผ ์๋ channel์ ์ฐจ์ด์ ์ ๋ฌด์์ ๋๊น?
๋ฒํผ ์๋ channel(make(chan T))์ ์ก์ ์ธก๊ณผ ์์ ์ธก์ด ๋์์ ์ค๋น๋์ด ์์ด์ผ ํฉ๋๋ค. ์ก์ ์ ๋ค๋ฅธ goroutine์ด ์์ ํ ๋๊น์ง ๋ธ๋กํน๋ฉ๋๋ค. ๋ฒํผ ์๋ channel(make(chan T, n))์ ๋ธ๋กํน ์์ด ์ต๋ n๊ฐ์ ๊ฐ์ ์ก์ ํ ์ ์์ต๋๋ค.
package main
import "fmt"
func main() {
// Unbuffered: send blocks until receive is ready
ch := make(chan string)
go func() {
ch <- "hello" // blocks here until main reads
}()
msg := <-ch
fmt.Println(msg)
// Buffered: send does not block until buffer is full
buf := make(chan int, 3)
buf <- 1 // does not block (buffer has space)
buf <- 2 // does not block
buf <- 3 // does not block
// buf <- 4 would block โ buffer is full
fmt.Println(<-buf, <-buf, <-buf) // 1 2 3
}๋ฉด์ ๊ด์ด ์์ฃผ ์ถ๊ฐํ๋ ์ง๋ฌธ์ "์ธ์ ์ด๋ค ๊ฒ์ ์ ํํฉ๋๊น?"์ ๋๋ค. ๋ฒํผ ์๋ channel์ ๋๊ธฐํ๋ฅผ ๊ฐ์ ํ๋ฏ๋ก, ์ก์ ์ธก์ด ์์ ์ธก์ ์ฒ๋ฆฌ ์๋ฃ๋ฅผ ํ์ธํด์ผ ํ๋ ๊ฒฝ์ฐ์ ์ ํฉํฉ๋๋ค. ๋ฒํผ ์๋ channel์ ์ก์ ์ธก๊ณผ ์์ ์ธก์ ํ์ด๋ฐ์ ๋ถ๋ฆฌํ๋ฏ๋ก, ์ํฌ ํ๋ ์๋ ์ ํ์ฒ๋ผ ์ด๋ ์ ๋์ ์ฌ์ ๊ฐ ํ์ฉ๋๋ ์ํฉ์ ์ ํฉํฉ๋๋ค.
Q: channel์ closeํ๋ฉด ์ด๋ป๊ฒ ๋ฉ๋๊น?
channel์ closeํ๋ฉด ๋ ์ด์ ๊ฐ์ด ์ก์ ๋์ง ์์์ ์๋ฆฝ๋๋ค. close๋ channel์์์ ์์ ์ ์ ๋ก ๊ฐ์ ์ฆ์ ๋ฐํํฉ๋๋ค. close๋ channel์ ์ก์ ํ๋ฉด panic์ด ๋ฐ์ํฉ๋๋ค. range ๋ฃจํ๋ channel์ด close๋๋ฉด ์๋์ผ๋ก ์ข
๋ฃ๋ฉ๋๋ค.
package main
import "fmt"
func producer(ch chan<- int, count int) {
for i := 0; i < count; i++ {
ch <- i
}
close(ch) // signal: no more values
}
func main() {
ch := make(chan int, 5)
go producer(ch, 5)
// range exits automatically when channel closes
for val := range ch {
fmt.Println("received:", val)
}
// Reading from closed channel returns zero value + false
val, ok := <-ch
fmt.Printf("after close: val=%d, ok=%v\n", val, ok)
}ํต์ฌ ์ฃผ์์ฌํญ์ผ๋ก, channel์ closeํ๋ ๊ฒ์ ์ก์ ์ธก๋ง ํด์ผ ํ๋ฉฐ ์์ ์ธก์ด closeํด์๋ ์ ๋ฉ๋๋ค. ๋ค๋ฅธ goroutine์ด ์์ง ์ฐ๊ธฐ ์ค์ธ channel์ closeํ๋ฉด panic์ด ๋ฐ์ํฉ๋๋ค.
Go ๋ฉด์ ์ค๋น๊ฐ ๋์ จ๋์?
์ธํฐ๋ํฐ๋ธ ์๋ฎฌ๋ ์ดํฐ, flashcards, ๊ธฐ์ ํ ์คํธ๋ก ์ฐ์ตํ์ธ์.
select ๋ฌธ: channel ๋ค์คํ
Q: select๋ ์ด๋ป๊ฒ ๋์ํ๋ฉฐ, ์ฌ๋ฌ case๊ฐ ๋์์ ์ค๋น๋ ๊ฒฝ์ฐ ์ด๋ป๊ฒ ๋ฉ๋๊น?
select ๋ฌธ์ channel ์ฐ์ฐ ์ค ํ๋๊ฐ ์คํ ๊ฐ๋ฅํด์ง ๋๊น์ง ๋ธ๋กํน๋ฉ๋๋ค. ์ฌ๋ฌ case๊ฐ ๋์์ ์ค๋น๋ ๊ฒฝ์ฐ, Go๋ ๋ฌด์์๋ก ํ๋๋ฅผ ์ ํํฉ๋๋ค. ์ด๋ฅผ ํตํด ํน์ case๊ฐ ๊ธฐ์ ์ํ์ ๋น ์ง๋ ๊ฒ์ ๋ฐฉ์งํฉ๋๋ค.
package main
import (
"context"
"fmt"
"time"
)
func fetchFromAPI(ctx context.Context, url string) (string, error) {
resultCh := make(chan string, 1)
errCh := make(chan error, 1)
go func() {
// Simulate API call
time.Sleep(200 * time.Millisecond)
resultCh <- fmt.Sprintf("data from %s", url)
}()
select {
case result := <-resultCh:
return result, nil
case err := <-errCh:
return "", err
case <-ctx.Done():
// Context cancelled or timed out
return "", ctx.Err()
}
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
defer cancel()
result, err := fetchFromAPI(ctx, "https://api.example.com/data")
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println(result)
}๋ฉด์ ๊ด์ด select๋ก ํ๊ฐํ๋ ๊ฒ์ ๋ ๊ฐ์ง์
๋๋ค. ๋ฌด์์ ์ ํ ๊ท์น์ ๋ํ ์ดํด, ๊ทธ๋ฆฌ๊ณ channel์ context.Context์ ๊ฒฐํฉํ์ฌ ํ์์์ ๋ฐ ์ทจ์ ํจํด์ ๊ตฌํํ๋ ๋ฅ๋ ฅ์
๋๋ค.
๋ฉด์ ์์ ์ถ์ ๋๋ ๋์์ฑ ํจํด
์๋์ด ๋ ๋ฒจ Go ๋ฉด์ ์์๋ ์๋ ํจํด ์ค ํ๋๋ฅผ ์ฒ์๋ถํฐ ๊ตฌํํ๋ ๋ฌธ์ ๊ฐ ๊ฑฐ์ ๋ฐ๋์ ์ถ์ ๋ฉ๋๋ค.
Fan-Out/Fan-In ํจํด
Q: ํญ๋ชฉ์ ๋์์ ์ฒ๋ฆฌํ๋ fan-out/fan-in ํ์ดํ๋ผ์ธ์ ๊ตฌํํ์ญ์์ค.
fan-out์ ์ฌ๋ฌ goroutine์ ์์ ์ ๋ถ๋ฐฐํฉ๋๋ค. fan-in์ ์ฌ๋ฌ goroutine์ ๊ฒฐ๊ณผ๋ฅผ ํ๋์ channel๋ก ์์งํฉ๋๋ค.
package main
import (
"fmt"
"sync"
)
// generator produces values on a channel
func generator(nums ...int) <-chan int {
out := make(chan int)
go func() {
for _, n := range nums {
out <- n
}
close(out)
}()
return out
}
// square reads from input, squares each value
func square(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for n := range in {
out <- n * n
}
close(out)
}()
return out
}
// fanIn merges multiple channels into one
func fanIn(channels ...<-chan int) <-chan int {
var wg sync.WaitGroup
merged := make(chan int)
for _, ch := range channels {
wg.Add(1)
go func(c <-chan int) {
defer wg.Done()
for val := range c {
merged <- val
}
}(ch)
}
go func() {
wg.Wait()
close(merged) // close after all inputs are drained
}()
return merged
}
func main() {
in := generator(2, 3, 4, 5, 6)
// Fan out: two goroutines reading from same channel
c1 := square(in)
c2 := square(in)
// Fan in: merge results
for result := range fanIn(c1, c2) {
fmt.Println(result)
}
}๋ฉด์ ๊ด์ด ๊ธฐ๋ํ๋ ํต์ฌ ํต์ฐฐ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค. generator์ channel์ c1๊ณผ c2 ์ฌ์ด์ ๊ณต์ ๋๋ฏ๋ก, ๊ฐ ๊ฐ์ ์ ํํ ํ๋์ ์์ปค์ ์ํด์๋ง ์ฒ๋ฆฌ๋ฉ๋๋ค(์ค๋ณต ์ฒ๋ฆฌ๋์ง ์์). fanIn ํจ์๋ WaitGroup์ ์ฌ์ฉํ์ฌ ๋ชจ๋ ์
๋ ฅ channel์ด ์์ง๋ ํ์ ๋ณํฉ๋ channel์ closeํฉ๋๋ค.
errgroup์ ํ์ฉํ ์์ปค ํ
Q: ์๋ฌ ํธ๋ค๋ง์ด ํฌํจ๋ ์ ํ๋ ์์ปค ํ์ ์ด๋ป๊ฒ ๊ตฌํํฉ๋๊น?
golang.org/x/sync/errgroup ํจํค์ง(Go ํ์ฅ ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ)๊ฐ ์ด ๋ฌธ์ ๋ฅผ ๊น๋ํ๊ฒ ํด๊ฒฐํฉ๋๋ค. goroutine ๋ผ์ดํ์ฌ์ดํด ๊ด๋ฆฌ, ์ฒซ ๋ฒ์งธ ์๋ฌ ์์ง, context์์ ํตํฉ์ ํตํ ์ทจ์๋ฅผ ์ฒ๋ฆฌํฉ๋๋ค.
package main
import (
"context"
"fmt"
"golang.org/x/sync/errgroup"
)
func processItem(ctx context.Context, id int) error {
// Check for cancellation before heavy work
select {
case <-ctx.Done():
return ctx.Err()
default:
}
if id == 7 {
return fmt.Errorf("failed to process item %d", id)
}
fmt.Printf("processed item %d\n", id)
return nil
}
func main() {
g, ctx := errgroup.WithContext(context.Background())
g.SetLimit(3) // maximum 3 concurrent goroutines
for i := 0; i < 10; i++ {
id := i
g.Go(func() error {
return processItem(ctx, id)
})
}
// Wait blocks until all goroutines finish
// Returns the first non-nil error
if err := g.Wait(); err != nil {
fmt.Println("pipeline error:", err)
}
}2026๋
์ด ๊ธฐ์ค ํ์ฌ ์์ ๋ฆด๋ฆฌ์ค์ธ Go 1.24์์๋ ์ด ํจํด์ด ๊ถ์ฅ ์ ๊ทผ๋ฒ์ผ๋ก ์ ์ง๋๊ณ ์์ต๋๋ค. SetLimit ๋ฉ์๋๋ Go 1.20์์ ์ถ๊ฐ๋์ด, ์ธ๋งํฌ์ด ๊ธฐ๋ฐ ๋์์ฑ ์ ํ์ ์๋์ผ๋ก ๊ตฌํํ ํ์๊ฐ ์์ด์ก์ต๋๋ค.
๋ ์ด์ค ์ปจ๋์ ๊ณผ sync ํ๋ฆฌ๋ฏธํฐ๋ธ
Q: Go์์ ๋ ์ด์ค ์ปจ๋์ ์ ์ด๋ป๊ฒ ๊ฐ์งํ๊ณ ๋ฐฉ์งํฉ๋๊น?
Go๋ -race ํ๋๊ทธ๋ก ํ์ฑํ๋๋ ๋ด์ฅ ๋ ์ด์ค ๋ํ
ํฐ๋ฅผ ์ ๊ณตํฉ๋๋ค. ๋ฐํ์์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ๋ํ ๋น๋๊ธฐํ๋ ๋์ ์ ๊ทผ์ ๊ฐ์งํฉ๋๋ค.
package main
import (
"fmt"
"sync"
"sync/atomic"
)
// BAD: race condition โ do not use in production
func unsafeCounter() int {
counter := 0
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
counter++ // DATA RACE: concurrent read/write
}()
}
wg.Wait()
return counter // result is non-deterministic
}
// GOOD: atomic operations for simple counters
func atomicCounter() int64 {
var counter atomic.Int64
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
counter.Add(1) // thread-safe atomic increment
}()
}
wg.Wait()
return counter.Load() // always 1000
}
// GOOD: mutex for complex shared state
type SafeMap struct {
mu sync.RWMutex
data map[string]int
}
func (m *SafeMap) Set(key string, val int) {
m.mu.Lock() // exclusive lock for writes
defer m.mu.Unlock()
m.data[key] = val
}
func (m *SafeMap) Get(key string) (int, bool) {
m.mu.RLock() // shared lock for reads
defer m.mu.RUnlock()
v, ok := m.data[key]
return v, ok
}
func main() {
fmt.Println("unsafe:", unsafeCounter()) // unpredictable
fmt.Println("atomic:", atomicCounter()) // always 1000
}๋ฉด์ ๋ต๋ณ์ ์ธ ๊ฐ์ง ๋๊ธฐํ ์ ๋ต์ ๋ค๋ฃจ์ด์ผ ํฉ๋๋ค. ๋ณต์กํ ๊ณต์ ์ํ์๋ sync.Mutex/sync.RWMutex, ๋จ์ ์นด์ดํฐ์ ํ๋๊ทธ์๋ sync/atomic, goroutine ๊ฐ ํต์ ์๋ channel("๋ฉ๋ชจ๋ฆฌ๋ฅผ ๊ณต์ ํ์ฌ ํต์ ํ์ง ๋ง๊ณ , ํต์ ์ผ๋ก ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๊ณต์ ํ๋ผ")์ ์ฌ์ฉํฉ๋๋ค. go test -race ./... ์คํ์ ๋ชจ๋ CI ํ์ดํ๋ผ์ธ์ ํฌํจ๋์ด์ผ ํฉ๋๋ค.
Context์ ์ทจ์ ํจํด
Q: context.Context๊ฐ goroutine์ ๋ผ์ดํ์ฌ์ดํด์ ์ด๋ป๊ฒ ์ ์ดํ๋์ง ์ค๋ช ํ์ญ์์ค.
context ํจํค์ง๋ ์ทจ์ ์๊ทธ๋, ๋ฐ๋๋ผ์ธ, ์์ฒญ ๋ฒ์ ๊ฐ์ goroutine ๊ฒฝ๊ณ๋ฅผ ๋์ด ์ ํํ๋ ๋ฉ์ปค๋์ฆ์ ์ ๊ณตํฉ๋๋ค. ์ฅ์๊ฐ ์คํ๋๋ ๋ชจ๋ goroutine์ ์ฒซ ๋ฒ์งธ ๋งค๊ฐ๋ณ์๋ก context.Context๋ฅผ ๋ฐ์์ผ ํฉ๋๋ค.
package main
import (
"context"
"fmt"
"time"
)
// worker simulates a long-running task
func worker(ctx context.Context, id int, results chan<- string) {
select {
case <-time.After(time.Duration(id*100) * time.Millisecond):
results <- fmt.Sprintf("worker %d: done", id)
case <-ctx.Done():
results <- fmt.Sprintf("worker %d: cancelled (%v)", id, ctx.Err())
}
}
func main() {
// Parent context with 250ms deadline
ctx, cancel := context.WithTimeout(context.Background(), 250*time.Millisecond)
defer cancel()
results := make(chan string, 5)
// Launch 5 workers with increasing durations
for i := 1; i <= 5; i++ {
go worker(ctx, i, results)
}
// Collect all results
for i := 0; i < 5; i++ {
fmt.Println(<-results)
}
}์์ปค 1๊ณผ 2๋ 250ms ๋ฐ๋๋ผ์ธ ๋ด์ ์๋ฃ๋ฉ๋๋ค. ์์ปค 3, 4, 5๋ ctx.Done()์ ํตํด ์ทจ์ ์๊ทธ๋์ ์์ ํฉ๋๋ค. ์ด ํจํด์ Go์์ ๋ณต์๋ ฅ ์๋ HTTP ์๋ฒ์ ๋ง์ดํฌ๋ก์๋น์ค๋ฅผ ๊ตฌ์ถํ๋ ๋ฐ ๊ทผ๋ณธ์ด ๋๋ ๊ฒ์
๋๋ค. ๋ชจ๋ ์์ฒญ ํธ๋ค๋ฌ๋ ํด๋ผ์ด์ธํธ ์ฐ๊ฒฐ์ด ๋์ด์ง ๋ ์ทจ์๊ฐ ์ ํ๋๋ context๋ฅผ ์์ ํฉ๋๋ค.
context.Context๋ฅผ ๊ตฌ์กฐ์ฒด ํ๋์ ์ ์ฅํด์๋ ์ ๋ฉ๋๋ค. Go ๊ณต์ ๋ฌธ์์๋ "Context๋ฅผ ๊ตฌ์กฐ์ฒด ํ์
๋ด๋ถ์ ์ ์ฅํ์ง ๋ง์ญ์์ค. ๋์ , Context๊ฐ ํ์ํ ๊ฐ ํจ์์ ๋ช
์์ ์ผ๋ก ์ ๋ฌํ์ญ์์ค"๋ผ๊ณ ๋ช
์๋์ด ์์ต๋๋ค. ๋ฉด์ ๊ด์ ์ด ์ง๋ฌธ์ผ๋ก ํ๋ณด์๊ฐ Go ๊ด๋ก๋ฅผ ๋ฐ๋ฅด๋์ง ํ์ธํฉ๋๋ค.
๋ฐ๋๋ฝ ๊ฐ์ง: ๋ฉด์ ๋์ด๋ ๋์ ์ง๋ฌธ
Q: ์ด ์ฝ๋๋ ๋ฐ๋๋ฝ์ด ๋ฐ์ํฉ๋๊น? ๊ทธ ์ด์ ๋ ๋ฌด์์ ๋๊น?
๋ฐ๋๋ฝ ์ง๋ฌธ์ ํ๋ณด์๊ฐ goroutine ์ค์ผ์ค๋ง๊ณผ channel ์ฐ์ฐ์ ๋ํด ์ถ๋ก ํ๋ ๋ฅ๋ ฅ์ ํ ์คํธํ๋ฏ๋ก ์์ฃผ ์ถ์ ๋ฉ๋๋ค.
package main
func main() {
ch := make(chan int)
ch <- 42 // DEADLOCK: unbuffered send with no receiver
// The main goroutine blocks here forever
// Go runtime detects this: "fatal error: all goroutines are asleep"
}์์ ๋ฐฉ๋ฒ์ ๊ฐ๋จํฉ๋๋ค. channel์ ๋ฒํผ ์๋ ๊ฒ์ผ๋ก ๋ณ๊ฒฝํ๊ฑฐ๋(make(chan int, 1)), ์ก์ ์ ์ ์์ ์ฉ goroutine์ ์์ฑํฉ๋๋ค. Go ๋ฐํ์์ ๋ชจ๋ goroutine์ด ๋ธ๋กํน๋ ๊ฒฝ์ฐ ๋ฐ๋๋ฝ์ ๊ฐ์งํ์ง๋ง, ๋ชจ๋ goroutine์ด ์ฌ๋ฆฝ ์ํ์ธ ๊ฒฝ์ฐ์๋ง ํด๋น๋ฉ๋๋ค. ๋จ ํ๋์ goroutine์ด๋ผ๋ ์คํ ์ค์ด๋ฉด(์: ๋ฐฑ๊ทธ๋ผ์ด๋ HTTP ์๋ฒ), ๋ฐํ์์ ๋ถ๋ถ์ ๋ฐ๋๋ฝ์ ๊ฐ์งํ์ง ๋ชปํฉ๋๋ค.
Go ๋ฐํ์์ด ๋ฐ๋๋ฝ์ ๊ฐ์งํ๋ ๊ฒ์ ํ๋ก๊ทธ๋จ ๋ด์ ๋ชจ๋ goroutine์ด ๋ธ๋กํน๋ ๊ฒฝ์ฐ์๋ง ํด๋น๋ฉ๋๋ค. HTTP ์๋ฒ๋ ๋ฐฑ๊ทธ๋ผ์ด๋ ์์ปค๊ฐ ์๋ ์ค์ ์ ํ๋ฆฌ์ผ์ด์
์์๋ ๋ฐ๋๋ฝ ์ํ์ goroutine ๋์๊ฐ ๋ฐํ์ ๋ํ
ํฐ์ ์ํด ๊ฐ์ง๋์ง ์์ต๋๋ค. ํ๋ก๋์
ํ๊ฒฝ์์ ์ด๋ฌํ ๋ฌธ์ ๋ฅผ ์ง๋จํ๋ ค๋ฉด pprof์ goroutine ๋คํ(runtime.Stack) ๊ฐ์ ๋๊ตฌ๊ฐ ํ์ํฉ๋๋ค.
๊ณ ๊ธ ํจํด: ์๋ ์ ํ ๋์ ์ฒ๋ฆฌ
Q: ์๋ ์ ํ์ด ์ ์ฉ๋ ๋์ API ํธ์ถ์ ์ด๋ป๊ฒ ๊ตฌํํฉ๋๊น?
์ด ์ง๋ฌธ์ ์ฌ๋ฌ ๋์์ฑ ํ๋ฆฌ๋ฏธํฐ๋ธ๋ฅผ ํ๋์ ํตํฉ๋ ์๋ฃจ์ ์ผ๋ก ์กฐํฉํ๋ ๋ฅ๋ ฅ์ ํ๊ฐํฉ๋๋ค.
package main
import (
"context"
"fmt"
"sync"
"time"
)
// RateLimiter controls concurrent and temporal access
type RateLimiter struct {
semaphore chan struct{} // limits concurrency
ticker *time.Ticker // limits rate
}
func NewRateLimiter(maxConcurrent int, interval time.Duration) *RateLimiter {
return &RateLimiter{
semaphore: make(chan struct{}, maxConcurrent),
ticker: time.NewTicker(interval),
}
}
func (rl *RateLimiter) Execute(ctx context.Context, fn func() error) error {
// Wait for rate limit tick
select {
case <-rl.ticker.C:
case <-ctx.Done():
return ctx.Err()
}
// Acquire concurrency slot
select {
case rl.semaphore <- struct{}{}:
case <-ctx.Done():
return ctx.Err()
}
defer func() { <-rl.semaphore }() // release slot
return fn()
}
func main() {
rl := NewRateLimiter(3, 100*time.Millisecond)
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
err := rl.Execute(ctx, func() error {
fmt.Printf("[%v] processing %d\n", time.Now().Format("04:05.000"), id)
time.Sleep(150 * time.Millisecond) // simulate work
return nil
})
if err != nil {
fmt.Printf("item %d: %v\n", id, err)
}
}(i)
}
wg.Wait()
}์ด ํจํด์ channel ๊ธฐ๋ฐ ์ธ๋งํฌ์ด(๋์์ฑ ์ ํ)์ ticker(์๋ ์ ํ)๋ฅผ ๊ฒฐํฉํฉ๋๋ค. context ์ฒดํฌ๊ฐ ํฌํจ๋ ์ด์ค select๊ฐ ๊ทธ๋ ์ด์คํ ์
ง๋ค์ด์ ๋ณด์ฅํฉ๋๋ค. ์ด์ ๊ฐ์ ํ๋ก๋์
์์ค์ ๋ต๋ณ์ด ์๋์ด ํ๋ณด์๋ฅผ ์ฐจ๋ณํํฉ๋๋ค.
์ฐ์ต์ ์์ํ์ธ์!
๋ฉด์ ์๋ฎฌ๋ ์ดํฐ์ ๊ธฐ์ ํ ์คํธ๋ก ์ง์์ ํ ์คํธํ์ธ์.
๊ฒฐ๋ก
- goroutine์ M:N ์ค์ผ์ค๋ง์ ๊ฐ์ง Go ๋ฐํ์ ๊ด๋ฆฌ ์ ์ ๊ณต๊ฐ ์ค๋ ๋์ด๋ฉฐ, ์์ฑ๋ goroutine ๋ด์์ panic์ ๋ฐ๋์ ๋ณต๊ตฌํด์ผ ํ๋ค
- ๋ฒํผ ์๋ channel์ ์ก์ ์ธก๊ณผ ์์ ์ธก์ ๋๊ธฐํํ๊ณ , ๋ฒํผ ์๋ channel์ ํ์ด๋ฐ์ ๋ถ๋ฆฌํ๋ค. ์ก์ ์ธก์ด ํ์ธ์ ํ์๋ก ํ๋์ง์ ๋ฐ๋ผ ์ ํํ๋ค
- select ๋ฌธ์ ๋ฌด์์ ์ ํ์ผ๋ก channel ์ฐ์ฐ์ ๋ค์คํํ๋ค. ํ์์์ ์ฒ๋ฆฌ์๋
context.Context์ ๊ฒฐํฉํ์ฌ ์ฌ์ฉํ๋ค - fan-out/fan-in๊ณผ ์์ปค ํ(
errgroup.SetLimit์ฌ์ฉ)์ด ๋ฉด์ ์์ ๊ฐ์ฅ ์์ฃผ ์ถ์ ๋๋ ๋์์ฑ ํจํด์ด๋ค - ๋ณต์กํ ๊ณต์ ์ํ์๋
sync.Mutex, ๋จ์ ์นด์ดํฐ์๋sync/atomic, goroutine ๊ฐ ํต์ ์๋ channel์ ์ฌ์ฉํ๋ค - ๋ฐ์ดํฐ ๋ ์ด์ค ๊ฐ์ง๋ฅผ ์ํด CI์์
go test -race๋ฅผ ๋ฐ๋์ ์คํํ๋ค. ๋ถ๋ถ์ ๋ฐ๋๋ฝ ์ง๋จ์๋pprof๊ฐ ํ์ํ๋ค context.Context๋ฅผ ๊ตฌ์กฐ์ฒด์ ์ ์ฅํ์ง ์๊ณ , ์ฒซ ๋ฒ์งธ ํจ์ ๋งค๊ฐ๋ณ์๋ก ์ ๋ฌํ๋ค- Go์ ์๋ ์ ํ์ channel ์ธ๋งํฌ์ด์ ticker๋ฅผ ๊ฒฐํฉํ๊ณ , context ์ธ์ select ๋ฌธ์ผ๋ก ๋ํํ๋ค
์ฐ์ต์ ์์ํ์ธ์!
๋ฉด์ ์๋ฎฌ๋ ์ดํฐ์ ๊ธฐ์ ํ ์คํธ๋ก ์ง์์ ํ ์คํธํ์ธ์.

์์ฑ์
Anthony Fillion-Mailletํ์คํ ๊ฐ๋ฐ์, SharpSkill ์ฐฝ์ ์
10๋ ์ด์ ํ์คํ ๊ฐ๋ฐ์ ํด์์ต๋๋ค. SharpSkill์ ์ด์ํ๋ฉฐ ์ด๊ณณ์ ๊ฒ์๋๋ ๋ชจ๋ ๋ด์ฉ์ ์ฑ ์์ ์ง๋๋ค.
2026๋ 4์ 1์ผ ์ ๋ฐ์ดํธ
ํ๊ทธ
๊ณต์
๊ด๋ จ ๊ธฐ์ฌ

Go ๋์์ฑ: ๊ณ ๋ฃจํด๊ณผ ์ฑ๋ - ์๋ฒฝ ๊ฐ์ด๋
๊ณ ๋ฃจํด๊ณผ ์ฑ๋์ ์ฌ์ฉํด Go ๋์์ฑ์ ๋ง์คํฐํ์ธ์. ๊ณ ๊ธ ํจํด, ๋๊ธฐํ, select ๋ฌธ, ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ์์ธํ ์ฝ๋ ์์ ์ ํจ๊ป ๋ค๋ฃน๋๋ค.

Go ๋ฉด์ ํต์ฌ 25๋ฌธํญ: ๊ฐ๋ฐ์๋ฅผ ์ํ ์์ ๊ฐ์ด๋
Go ๋ฉด์ ์ ๊ฐ์ฅ ๋ง์ด ๋ฑ์ฅํ๋ 25๊ฐ ์ง๋ฌธ์ผ๋ก ํฉ๊ฒฉ์ ๋ ธ๋ฆฌ์ธ์. ๊ณ ๋ฃจํด, ์ฑ๋, ์ธํฐํ์ด์ค, ๋์์ฑ ํจํด์ ์ฝ๋ ์์ ๋ก ์ ๋ฆฌํ์ต๋๋ค.

Go: Java/Python ๊ฐ๋ฐ์๋ฅผ ์ํ ๊ธฐ์ด 2026
Java๋ Python ๊ฒฝํ์ ํ์ฉํ์ฌ Go๋ฅผ ๋น ๋ฅด๊ฒ ๋ฐฐ์ฐ์ธ์. ๊ณ ๋ฃจํด, ์ฑ๋, ์ธํฐํ์ด์ค ๋ฐ ์ํํ ์ ํ์ ์ํ ํต์ฌ ํจํด์ ์ค๋ช ํฉ๋๋ค.