⚡ GO 동시성 - 고루틴과 채널
📋 목차
1. 고루틴 기초
🚀 고루틴이란?
고루틴 (Goroutine)
고루틴은 Go 런타임에서 관리하는 경량 스레드입니다. OS 스레드보다 훨씬 가볍고 빠르며, 수천 개를 동시에 실행할 수 있습니다.
package main
import (
"fmt"
"time"
"runtime"
)
// 일반 함수
func sayHello(name string) {
for i := 0; i < 5; i++ {
fmt.Printf("Hello from %s: %d\n", name, i)
time.Sleep(100 * time.Millisecond)
}
}
// 고루틴으로 실행할 함수
func sayHelloGoroutine(name string) {
for i := 0; i < 5; i++ {
fmt.Printf("[Goroutine] Hello from %s: %d\n", name, i)
time.Sleep(100 * time.Millisecond)
}
}
func goroutineBasics() {
fmt.Printf("Go version: %s\n", runtime.Version())
fmt.Printf("CPU cores: %d\n", runtime.NumCPU())
fmt.Printf("Initial goroutines: %d\n", runtime.NumGoroutine())
// 일반 함수 호출 (순차 실행)
fmt.Println("\n=== Sequential Execution ===")
sayHello("Alice")
sayHello("Bob")
// 고루틴으로 실행 (동시 실행)
fmt.Println("\n=== Concurrent Execution ===")
go sayHelloGoroutine("Charlie") // 고루틴으로 실행
go sayHelloGoroutine("Diana") // 고루틴으로 실행
fmt.Printf("After launching goroutines: %d\n", runtime.NumGoroutine())
// 고루틴이 완료될 때까지 대기
time.Sleep(1 * time.Second)
fmt.Printf("Final goroutines: %d\n", runtime.NumGoroutine())
}🎭 익명 함수와 고루틴
func anonymousGoroutines() {
fmt.Println("=== Anonymous Goroutines ===")
// 익명 함수를 고루틴으로 실행
go func() {
fmt.Println("Anonymous goroutine 1")
time.Sleep(200 * time.Millisecond)
fmt.Println("Anonymous goroutine 1 done")
}()
// 매개변수가 있는 익명 함수
go func(message string, count int) {
for i := 0; i < count; i++ {
fmt.Printf("Anonymous goroutine 2: %s %d\n", message, i)
time.Sleep(150 * time.Millisecond)
}
}("Hello", 3)
// 클로저 사용 (주의: 변수 캡처)
for i := 0; i < 3; i++ {
// 잘못된 예시 - 모든 고루틴이 같은 i 값을 참조
go func() {
fmt.Printf("Wrong closure: %d\n", i) // 모두 3이 출력될 수 있음
}()
// 올바른 예시 - 매개변수로 값 전달
go func(num int) {
fmt.Printf("Correct closure: %d\n", num)
}(i)
// 또 다른 올바른 예시 - 지역 변수 생성
i := i // 지역 변수로 복사
go func() {
fmt.Printf("Local copy: %d\n", i)
}()
}
time.Sleep(1 * time.Second)
}🕐 고루틴 라이프사이클
import (
"context"
"sync"
)
func goroutineLifecycle() {
var wg sync.WaitGroup
// 1. 정상 완료되는 고루틴
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println("Task 1: Starting")
time.Sleep(500 * time.Millisecond)
fmt.Println("Task 1: Completed")
}()
// 2. 컨텍스트로 취소되는 고루틴
ctx, cancel := context.WithTimeout(context.Background(), 300*time.Millisecond)
defer cancel()
wg.Add(1)
go func() {
defer wg.Done()
select {
case <-time.After(1 * time.Second):
fmt.Println("Task 2: Completed")
case <-ctx.Done():
fmt.Println("Task 2: Cancelled due to timeout")
}
}()
// 3. 채널을 통한 종료 신호
quit := make(chan bool)
wg.Add(1)
go func() {
defer wg.Done()
for {
select {
case <-quit:
fmt.Println("Task 3: Received quit signal")
return
default:
fmt.Println("Task 3: Working...")
time.Sleep(100 * time.Millisecond)
}
}
}()
// 200ms 후 종료 신호 전송
time.Sleep(200 * time.Millisecond)
quit <- true
// 모든 고루틴 완료 대기
wg.Wait()
fmt.Println("All tasks completed")
}2. 채널 기초
📡 채널이란?
채널 (Channel)
채널은 고루틴 간 통신을 위한 파이프입니다. 타입 안전하며, 동기화 기능도 제공합니다.
func channelBasics() {
// 1. 채널 생성
messages := make(chan string) // unbuffered 채널
numbers := make(chan int, 3) // buffered 채널 (크기 3)
// 2. 고루틴에서 채널로 데이터 전송
go func() {
messages <- "Hello" // 채널에 값 전송 (blocked until received)
messages <- "World"
close(messages) // 채널 닫기
}()
// 3. 채널에서 데이터 수신
for message := range messages { // 채널이 닫힐 때까지 수신
fmt.Println("Received:", message)
}
// 4. 버퍼드 채널 사용
go func() {
for i := 1; i <= 5; i++ {
numbers <- i
fmt.Printf("Sent: %d (buffer size: 3)\n", i)
}
close(numbers)
}()
// 5. 채널 수신 패턴들
// 패턴 1: range로 모든 값 수신
for num := range numbers {
fmt.Printf("Received: %d\n", num)
time.Sleep(200 * time.Millisecond) // 처리 시간 시뮬레이션
}
// 패턴 2: ok 변수로 채널 상태 확인
testChannel := make(chan int, 1)
testChannel <- 42
close(testChannel)
if value, ok := <-testChannel; ok {
fmt.Printf("Channel is open, received: %d\n", value)
} else {
fmt.Println("Channel is closed")
}
if value, ok := <-testChannel; ok {
fmt.Printf("Channel is open, received: %d\n", value)
} else {
fmt.Println("Channel is closed (no more values)")
}
}🎛️ 채널 방향성
// 채널 방향성 지정
func channelDirections() {
messages := make(chan string)
// send-only 채널로 전달
go sender(messages)
// receive-only 채널로 전달
go receiver(messages)
time.Sleep(1 * time.Second)
}
// send-only 채널 (chan<- string)
func sender(ch chan<- string) {
messages := []string{"Hello", "Go", "Channels"}
for _, message := range messages {
ch <- message
fmt.Printf("Sent: %s\n", message)
time.Sleep(200 * time.Millisecond)
}
close(ch)
}
// receive-only 채널 (<-chan string)
func receiver(ch <-chan string) {
for message := range ch {
fmt.Printf("Received: %s\n", message)
}
fmt.Println("Receiver finished")
}🔄 select 문
select 문
select는 여러 채널 작업 중 실행 가능한 것을 선택하는 Go의 특별한 제어문입니다.
func selectBasics() {
ch1 := make(chan string)
ch2 := make(chan string)
quit := make(chan bool)
// 고루틴 1: ch1로 데이터 전송
go func() {
time.Sleep(200 * time.Millisecond)
ch1 <- "Message from channel 1"
}()
// 고루틴 2: ch2로 데이터 전송
go func() {
time.Sleep(100 * time.Millisecond)
ch2 <- "Message from channel 2"
}()
// 고루틴 3: 타임아웃 후 종료 신호
go func() {
time.Sleep(500 * time.Millisecond)
quit <- true
}()
// select로 여러 채널 동시 대기
for {
select {
case msg1 := <-ch1:
fmt.Printf("From ch1: %s\n", msg1)
case msg2 := <-ch2:
fmt.Printf("From ch2: %s\n", msg2)
case <-quit:
fmt.Println("Quit signal received")
return
case <-time.After(1 * time.Second):
fmt.Println("Timeout - no message received")
return
default:
fmt.Println("No channel is ready")
time.Sleep(50 * time.Millisecond)
}
}
}🎯 채널을 활용한 패턴들
// 1. Fan-out: 하나의 입력을 여러 고루틴으로 분산
func fanOut(input <-chan int) (<-chan int, <-chan int) {
out1 := make(chan int)
out2 := make(chan int)
go func() {
defer close(out1)
defer close(out2)
for val := range input {
// 두 채널로 동시에 전송
out1 <- val
out2 <- val
}
}()
return out1, out2
}
// 2. Fan-in: 여러 입력을 하나의 채널로 병합
func fanIn(ch1, ch2 <-chan string) <-chan string {
out := make(chan string)
go func() {
defer close(out)
for {
select {
case msg, ok := <-ch1:
if !ok {
ch1 = nil // 닫힌 채널을 nil로 설정해서 select에서 제외
} else {
out <- fmt.Sprintf("Ch1: %s", msg)
}
case msg, ok := <-ch2:
if !ok {
ch2 = nil
} else {
out <- fmt.Sprintf("Ch2: %s", msg)
}
}
// 두 채널이 모두 닫히면 종료
if ch1 == nil && ch2 == nil {
break
}
}
}()
return out
}
// 3. Worker Pool 패턴
func workerPool() {
jobs := make(chan int, 100)
results := make(chan int, 100)
// 워커 3개 시작
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
// 작업 전송
for j := 1; j <= 9; j++ {
jobs <- j
}
close(jobs)
// 결과 수집
for r := 1; r <= 9; r++ {
result := <-results
fmt.Printf("Result: %d\n", result)
}
}
func worker(id int, jobs <-chan int, results chan<- int) {
for job := range jobs {
fmt.Printf("Worker %d processing job %d\n", id, job)
time.Sleep(200 * time.Millisecond) // 작업 시뮬레이션
results <- job * job
}
}
func channelPatterns() {
fmt.Println("=== Fan-out Pattern ===")
input := make(chan int, 3)
// 입력 데이터 생성
go func() {
defer close(input)
for i := 1; i <= 3; i++ {
input <- i
}
}()
out1, out2 := fanOut(input)
// 두 출력 채널에서 데이터 수신
go func() {
for val := range out1 {
fmt.Printf("Out1 received: %d\n", val)
}
}()
go func() {
for val := range out2 {
fmt.Printf("Out2 received: %d\n", val)
}
}()
time.Sleep(500 * time.Millisecond)
fmt.Println("\n=== Fan-in Pattern ===")
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
defer close(ch1)
for i := 1; i <= 3; i++ {
ch1 <- fmt.Sprintf("Message %d", i)
time.Sleep(100 * time.Millisecond)
}
}()
go func() {
defer close(ch2)
for i := 1; i <= 3; i++ {
ch2 <- fmt.Sprintf("Data %d", i)
time.Sleep(150 * time.Millisecond)
}
}()
merged := fanIn(ch1, ch2)
for msg := range merged {
fmt.Printf("Merged: %s\n", msg)
}
fmt.Println("\n=== Worker Pool Pattern ===")
workerPool()
}3. 채널 고급 패턴
🎪 Pipeline 패턴
// 파이프라인 단계별 함수들
func generate(nums ...int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for _, num := range nums {
out <- num
}
}()
return out
}
func square(in <-chan int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for num := range in {
out <- num * num
}
}()
return out
}
func filter(in <-chan int, predicate func(int) bool) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for num := range in {
if predicate(num) {
out <- num
}
}
}()
return out
}
func pipelineExample() {
fmt.Println("=== Pipeline Pattern ===")
// 파이프라인 구성: generate -> square -> filter
numbers := generate(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
squared := square(numbers)
evens := filter(squared, func(n int) bool { return n%2 == 0 })
// 결과 출력
for result := range evens {
fmt.Printf("Even square: %d\n", result)
}
}🔄 Pub/Sub 패턴
import "sync"
type PubSub struct {
mu sync.RWMutex
subscribers map[string][]chan<- string
}
func NewPubSub() *PubSub {
return &PubSub{
subscribers: make(map[string][]chan<- string),
}
}
func (ps *PubSub) Subscribe(topic string) <-chan string {
ps.mu.Lock()
defer ps.mu.Unlock()
ch := make(chan string, 1)
ps.subscribers[topic] = append(ps.subscribers[topic], ch)
return ch
}
func (ps *PubSub) Publish(topic, message string) {
ps.mu.RLock()
defer ps.mu.RUnlock()
for _, ch := range ps.subscribers[topic] {
// Non-blocking send
select {
case ch <- message:
default:
fmt.Printf("Subscriber buffer full, dropping message: %s\n", message)
}
}
}
func (ps *PubSub) Unsubscribe(topic string, ch <-chan string) {
ps.mu.Lock()
defer ps.mu.Unlock()
subscribers := ps.subscribers[topic]
for i, subscriber := range subscribers {
if subscriber == ch {
// Remove from slice
ps.subscribers[topic] = append(subscribers[:i], subscribers[i+1:]...)
close(subscriber)
break
}
}
}
func pubSubExample() {
fmt.Println("=== Pub/Sub Pattern ===")
pubsub := NewPubSub()
// 구독자 1: 뉴스 토픽
newsSubscriber1 := pubsub.Subscribe("news")
go func() {
for msg := range newsSubscriber1 {
fmt.Printf("News Subscriber 1: %s\n", msg)
}
}()
// 구독자 2: 뉴스 토픽
newsSubscriber2 := pubsub.Subscribe("news")
go func() {
for msg := range newsSubscriber2 {
fmt.Printf("News Subscriber 2: %s\n", msg)
}
}()
// 구독자 3: 스포츠 토픽
sportsSubscriber := pubsub.Subscribe("sports")
go func() {
for msg := range sportsSubscriber {
fmt.Printf("Sports Subscriber: %s\n", msg)
}
}()
// 메시지 발행
time.Sleep(100 * time.Millisecond) // 구독자 준비 시간
pubsub.Publish("news", "Breaking: Go 1.22 Released!")
pubsub.Publish("sports", "Football: Team A wins 3-0")
pubsub.Publish("news", "Tech: New AI Model Announced")
pubsub.Publish("weather", "Sunny day ahead") // 구독자 없음
time.Sleep(500 * time.Millisecond)
}⚡ Rate Limiting 패턴
import "golang.org/x/time/rate"
// 토큰 버킷을 이용한 Rate Limiter
type TokenBucket struct {
tokens chan struct{}
ticker *time.Ticker
}
func NewTokenBucket(capacity int, refillRate time.Duration) *TokenBucket {
tb := &TokenBucket{
tokens: make(chan struct{}, capacity),
ticker: time.NewTicker(refillRate),
}
// 초기 토큰 채우기
for i := 0; i < capacity; i++ {
tb.tokens <- struct{}{}
}
// 주기적으로 토큰 보충
go func() {
for range tb.ticker.C {
select {
case tb.tokens <- struct{}{}:
default:
// 버킷이 가득 차면 무시
}
}
}()
return tb
}
func (tb *TokenBucket) TakeToken() bool {
select {
case <-tb.tokens:
return true
default:
return false
}
}
func (tb *TokenBucket) Stop() {
tb.ticker.Stop()
}
// Rate limiter를 사용한 API 호출 시뮬레이션
func rateLimitingExample() {
fmt.Println("=== Rate Limiting Pattern ===")
// 1초에 3개 토큰, 최대 5개 토큰 보유 가능
limiter := NewTokenBucket(5, 333*time.Millisecond)
defer limiter.Stop()
// 10개의 API 요청 시뮬레이션
for i := 1; i <= 10; i++ {
if limiter.TakeToken() {
fmt.Printf("API Request %d: Allowed\n", i)
// API 호출 시뮬레이션
go func(reqID int) {
time.Sleep(100 * time.Millisecond)
fmt.Printf("API Request %d: Completed\n", reqID)
}(i)
} else {
fmt.Printf("API Request %d: Rate limited\n", i)
}
time.Sleep(200 * time.Millisecond) // 요청 간격
}
time.Sleep(2 * time.Second)
// Go의 rate 패키지 사용 예제
fmt.Println("\n=== Using golang.org/x/time/rate ===")
// 초당 2개, 버스트 5개
rl := rate.NewLimiter(rate.Limit(2), 5)
for i := 1; i <= 8; i++ {
if rl.Allow() {
fmt.Printf("Request %d: Allowed\n", i)
} else {
fmt.Printf("Request %d: Rate limited\n", i)
}
time.Sleep(300 * time.Millisecond)
}
}4. 동기화 도구
🔒 Mutex와 RWMutex
import (
"math/rand"
"sync"
)
type SafeCounter struct {
mu sync.Mutex
value int
}
func (sc *SafeCounter) Increment() {
sc.mu.Lock()
defer sc.mu.Unlock()
sc.value++
}
func (sc *SafeCounter) GetValue() int {
sc.mu.Lock()
defer sc.mu.Unlock()
return sc.value
}
type SafeMap struct {
mu sync.RWMutex
data map[string]int
}
func NewSafeMap() *SafeMap {
return &SafeMap{
data: make(map[string]int),
}
}
func (sm *SafeMap) Set(key string, value int) {
sm.mu.Lock()
defer sm.mu.Unlock()
sm.data[key] = value
}
func (sm *SafeMap) Get(key string) (int, bool) {
sm.mu.RLock() // 읽기 전용 락
defer sm.mu.RUnlock()
value, ok := sm.data[key]
return value, ok
}
func (sm *SafeMap) GetAll() map[string]int {
sm.mu.RLock()
defer sm.mu.RUnlock()
// 복사본 반환
result := make(map[string]int)
for k, v := range sm.data {
result[k] = v
}
return result
}
func mutexExample() {
fmt.Println("=== Mutex Example ===")
counter := &SafeCounter{}
var wg sync.WaitGroup
// 10개 고루틴이 동시에 카운터 증가
for i := 0; i < 10; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
for j := 0; j < 100; j++ {
counter.Increment()
}
fmt.Printf("Goroutine %d finished\n", id)
}(i)
}
wg.Wait()
fmt.Printf("Final counter value: %d\n", counter.GetValue())
fmt.Println("\n=== RWMutex Example ===")
safeMap := NewSafeMap()
// 데이터 쓰기 고루틴
wg.Add(1)
go func() {
defer wg.Done()
for i := 0; i < 5; i++ {
key := fmt.Sprintf("key%d", i)
value := rand.Intn(100)
safeMap.Set(key, value)
fmt.Printf("Set %s = %d\n", key, value)
time.Sleep(200 * time.Millisecond)
}
}()
// 데이터 읽기 고루틴들
for i := 0; i < 3; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
for j := 0; j < 10; j++ {
all := safeMap.GetAll()
fmt.Printf("Reader %d: %v\n", id, all)
time.Sleep(100 * time.Millisecond)
}
}(i)
}
wg.Wait()
}🎯 sync.Once와 sync.Pool
type Singleton struct {
value string
}
var (
instance *Singleton
once sync.Once
)
func GetSingleton() *Singleton {
once.Do(func() {
fmt.Println("Creating singleton instance")
instance = &Singleton{value: "I am singleton"}
})
return instance
}
func syncOnceExample() {
fmt.Println("=== sync.Once Example ===")
var wg sync.WaitGroup
// 여러 고루틴이 동시에 싱글톤 접근
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
s := GetSingleton()
fmt.Printf("Goroutine %d: %s\n", id, s.value)
}(i)
}
wg.Wait()
}
// sync.Pool 예제
type ExpensiveObject struct {
data [1000]byte
}
var objectPool = sync.Pool{
New: func() interface{} {
fmt.Println("Creating new expensive object")
return &ExpensiveObject{}
},
}
func useExpensiveObject() {
// 풀에서 객체 가져오기
obj := objectPool.Get().(*ExpensiveObject)
// 사용
fmt.Println("Using expensive object")
time.Sleep(100 * time.Millisecond)
// 다시 풀에 반환
objectPool.Put(obj)
}
func syncPoolExample() {
fmt.Println("\n=== sync.Pool Example ===")
var wg sync.WaitGroup
// 여러 고루틴이 객체 사용
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
fmt.Printf("Goroutine %d ", id)
useExpensiveObject()
}(i)
}
wg.Wait()
}🎯 실습 예제
📝 연습 문제 1: 웹 크롤러
package main
import (
"fmt"
"net/http"
"sync"
"time"
)
type CrawlResult struct {
URL string
StatusCode int
Error error
Duration time.Duration
}
type WebCrawler struct {
maxWorkers int
visitedMutex sync.Mutex
visited map[string]bool
results chan CrawlResult
}
func NewWebCrawler(maxWorkers int) *WebCrawler {
return &WebCrawler{
maxWorkers: maxWorkers,
visited: make(map[string]bool),
results: make(chan CrawlResult),
}
}
func (wc *WebCrawler) isVisited(url string) bool {
wc.visitedMutex.Lock()
defer wc.visitedMutex.Unlock()
if wc.visited[url] {
return true
}
wc.visited[url] = true
return false
}
func (wc *WebCrawler) crawlURL(url string) {
if wc.isVisited(url) {
return
}
start := time.Now()
resp, err := http.Get(url)
duration := time.Since(start)
result := CrawlResult{
URL: url,
Duration: duration,
Error: err,
}
if err == nil {
result.StatusCode = resp.StatusCode
resp.Body.Close()
}
wc.results <- result
}
func (wc *WebCrawler) Crawl(urls []string) []CrawlResult {
jobs := make(chan string, len(urls))
var wg sync.WaitGroup
// 워커 시작
for i := 0; i < wc.maxWorkers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for url := range jobs {
wc.crawlURL(url)
}
}()
}
// 결과 수집 고루틴
var results []CrawlResult
done := make(chan bool)
go func() {
for result := range wc.results {
results = append(results, result)
if len(results) == len(urls) {
done <- true
break
}
}
}()
// URL 작업 전송
for _, url := range urls {
jobs <- url
}
close(jobs)
// 완료 대기
<-done
wg.Wait()
close(wc.results)
return results
}
func webCrawlerExample() {
fmt.Println("=== Web Crawler Example ===")
urls := []string{
"https://golang.org",
"https://github.com",
"https://stackoverflow.com",
"https://reddit.com",
"https://news.ycombinator.com",
"https://invalid-url-that-should-fail.com",
}
crawler := NewWebCrawler(3) // 3개 워커
results := crawler.Crawl(urls)
fmt.Printf("\nCrawl Results (%d URLs):\n", len(results))
fmt.Println("----------------------------------------")
for _, result := range results {
status := fmt.Sprintf("Status: %d", result.StatusCode)
if result.Error != nil {
status = fmt.Sprintf("Error: %v", result.Error)
}
fmt.Printf("%-30s | %-20s | Duration: %v\n",
result.URL, status, result.Duration)
}
}📝 연습 문제 2: 실시간 로그 프로세서
import (
"bufio"
"regexp"
"strings"
)
type LogEntry struct {
Timestamp string
Level string
Message string
Raw string
}
type LogProcessor struct {
logPattern *regexp.Regexp
filters []LogFilter
processors []chan LogEntry
stats *LogStats
}
type LogFilter func(LogEntry) bool
type LogStats struct {
mu sync.RWMutex
totalLogs int
logsByLevel map[string]int
errorPatterns map[string]int
}
func NewLogStats() *LogStats {
return &LogStats{
logsByLevel: make(map[string]int),
errorPatterns: make(map[string]int),
}
}
func (ls *LogStats) Record(entry LogEntry) {
ls.mu.Lock()
defer ls.mu.Unlock()
ls.totalLogs++
ls.logsByLevel[entry.Level]++
// 에러 패턴 감지
if entry.Level == "ERROR" {
if strings.Contains(entry.Message, "database") {
ls.errorPatterns["database"]++
} else if strings.Contains(entry.Message, "network") {
ls.errorPatterns["network"]++
} else if strings.Contains(entry.Message, "timeout") {
ls.errorPatterns["timeout"]++
}
}
}
func (ls *LogStats) GetStats() map[string]interface{} {
ls.mu.RLock()
defer ls.mu.RUnlock()
return map[string]interface{}{
"total_logs": ls.totalLogs,
"logs_by_level": copyMap(ls.logsByLevel),
"error_patterns": copyMap(ls.errorPatterns),
}
}
func copyMap(m map[string]int) map[string]int {
result := make(map[string]int)
for k, v := range m {
result[k] = v
}
return result
}
func NewLogProcessor() *LogProcessor {
// 로그 패턴: 2025-11-26 10:30:45 [INFO] Message
pattern := regexp.MustCompile(`(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) \[(\w+)\] (.+)`)
return &LogProcessor{
logPattern: pattern,
stats: NewLogStats(),
}
}
func (lp *LogProcessor) AddFilter(filter LogFilter) {
lp.filters = append(lp.filters, filter)
}
func (lp *LogProcessor) AddProcessor(processor chan LogEntry) {
lp.processors = append(lp.processors, processor)
}
func (lp *LogProcessor) ParseLog(line string) *LogEntry {
matches := lp.logPattern.FindStringSubmatch(line)
if len(matches) != 4 {
return nil
}
return &LogEntry{
Timestamp: matches[1],
Level: matches[2],
Message: matches[3],
Raw: line,
}
}
func (lp *LogProcessor) ProcessLogs(logLines []string) {
var wg sync.WaitGroup
// 프로세서 고루틴들 시작
for i, processor := range lp.processors {
wg.Add(1)
go func(id int, ch chan LogEntry) {
defer wg.Done()
fmt.Printf("Processor %d started\n", id)
for entry := range ch {
// 프로세서별 로직 (여기서는 출력만)
fmt.Printf("Processor %d: [%s] %s - %s\n",
id, entry.Level, entry.Timestamp, entry.Message)
time.Sleep(50 * time.Millisecond) // 처리 시뮬레이션
}
fmt.Printf("Processor %d finished\n", id)
}(i, processor)
}
// 로그 처리
for _, line := range logLines {
entry := lp.ParseLog(line)
if entry == nil {
continue
}
// 필터 적용
passed := true
for _, filter := range lp.filters {
if !filter(*entry) {
passed = false
break
}
}
if !passed {
continue
}
// 통계 기록
lp.stats.Record(*entry)
// 모든 프로세서에 전송
for _, processor := range lp.processors {
processor <- *entry
}
}
// 프로세서 채널 닫기
for _, processor := range lp.processors {
close(processor)
}
wg.Wait()
}
func logProcessorExample() {
fmt.Println("=== Log Processor Example ===")
// 샘플 로그 데이터
logLines := []string{
"2025-11-26 10:30:45 [INFO] Application started",
"2025-11-26 10:30:46 [DEBUG] Loading configuration",
"2025-11-26 10:30:47 [INFO] Database connected",
"2025-11-26 10:30:48 [WARN] High memory usage detected",
"2025-11-26 10:30:49 [ERROR] Database connection timeout",
"2025-11-26 10:30:50 [ERROR] Network error: connection refused",
"2025-11-26 10:30:51 [INFO] User logged in",
"2025-11-26 10:30:52 [ERROR] Database query failed",
"Invalid log line",
"2025-11-26 10:30:53 [INFO] Request processed successfully",
}
processor := NewLogProcessor()
// 필터 추가: DEBUG 레벨 제외
processor.AddFilter(func(entry LogEntry) bool {
return entry.Level != "DEBUG"
})
// 프로세서 추가
errorProcessor := make(chan LogEntry, 10)
infoProcessor := make(chan LogEntry, 10)
processor.AddProcessor(errorProcessor)
processor.AddProcessor(infoProcessor)
// 로그 처리 실행
processor.ProcessLogs(logLines)
// 통계 출력
fmt.Println("\n=== Log Statistics ===")
stats := processor.stats.GetStats()
fmt.Printf("Total logs processed: %v\n", stats["total_logs"])
fmt.Printf("Logs by level: %v\n", stats["logs_by_level"])
fmt.Printf("Error patterns: %v\n", stats["error_patterns"])
}✅ 체크리스트
고루틴
- 고루틴 기본 개념과 생성
- 익명 함수와 고루틴
- 클로저와 변수 캡처 주의사항
- 고루틴 생명주기 관리
채널
- 채널 기본 사용법
- 버퍼드 vs 언버퍼드 채널
- 채널 방향성 지정
- select 문 활용
동시성 패턴
- Fan-out/Fan-in 패턴
- Worker Pool 패턴
- Pipeline 패턴
- Pub/Sub 패턴
동기화
- Mutex와 RWMutex
- sync.Once와 sync.Pool
- WaitGroup 사용법
- Context를 통한 취소
다음 단계
동시성을 익혔다면 GO 실전 REST API 프로젝트로 넘어가세요!