인셔셔RSS 관심 있는 블로그, 뉴스, 기술 정보를 효율적으로 추적하고 읽으세요
원문 읽기 InertiaRSS에서 열기

추천 피드

Google DeepMind News
Google DeepMind News
人人都是产品经理
人人都是产品经理
M
MIT News - Artificial intelligence
博客园 - 叶小钗
MyScale Blog
MyScale Blog
V
Visual Studio Blog
月光博客
月光博客
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
量子位
I
InfoQ
有赞技术团队
有赞技术团队
阮一峰的网络日志
阮一峰的网络日志
Jina AI
Jina AI
V
V2EX
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Blog — PlanetScale
Blog — PlanetScale
Last Week in AI
Last Week in AI
雷峰网
雷峰网
Stack Overflow Blog
Stack Overflow Blog
博客园 - Franky

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python)
Go 동시성의 침묵하는 살인자: Mutex, 세마포어, 그리고 Goroutine 누수
amir · 2026-05-25 · via DEV Community

Go는 동시성을 간단하게 보이게 합니다.

당신은 작성합니다:

go func() {
    // do something concurrently
}()

전체 화면 모드 입력 전체 화면 모드 종료

그리고 갑자기 당신의 코드가 다른 고루틴에서 실행됩니다.

그러한 간단함은 나가 Go를 매우 좋아하는 이유 중 하나입니다. 하지만 배후 시스템, 알림 파이프라인, 높은 트래픽 API 및 실제 부하 하에서 생산 서비스를 작업한 후, 중요한 것을 배웠습니다:

Go에서 발생하는 대부분의 동시성 문제는 동시성을 사용하지 않지 않기 때문이 아닙니다.

그들은 실제 병목 현상이 어디인지 이해하지 못하고 동시성을 사용하기 때문에 발생합니다.

때로는 잠금이 부족한 문제입니다.

하지만 매우 자주, 특히 생산 환경의 Go 서비스에서는 문제가 반대입니다.

  • 너무 많은 잠금
  • 너무 오랫동안 유지되는 잠금
  • 중요 섹션 내에서 네트워크 I/O
  • 종료하지 않는 고루틴
  • 무한한 고루틴 생성
  • 값으로 복사된 WaitGroups
  • 취소 전략 없이 사용되는 채널

이 글에서는 실제 시스템에서 본 동시성 문제들, 뮤텍스와 세마포어에 대한 제 추론 방법, 그리고 생산 환경 사고로 이어질 전까지 이러한 문제들을 디버깅하는 방법을 살펴보고자 합니다.


실제 문제: 우연히 순차적으로 되는 동시성

서비스는 바깥에서 동시성처럼 보일 수 있지만 내부적으로는 단일 스레드 애플리케이션처럼 동작할 수 있습니다.

이런 일은 요청 흐름의 대부분이 하나의 공유 락으로 숨겨져 있을 때 자주 발생합니다.

이런 패턴은 많은 개발자들이 인정하지 않는 것보다 더 흔합니다.

mu.Lock()
user.Name = "Test User"
sendEmail(user)
callDatabase(user)
mu.Unlock()

전체 화면 모드를 입력합니다. 전체 화면 모드를 종료합니다.

처음 보기에는 안전해 보일 수 있습니다.

개발자는 공유 상태를 보호하고자 했습니다. 그 부분은 합리적입니다. 하지만 이제 락은 공유 메모리를 넘어서는 것을 보호하고 있습니다. 전체 흐름을 보호하고 있습니다:

  1. 필드를 업데이트
  2. 이메일을 전송
  3. 데이터베이스를 호출
  4. 네트워크 I/O를 기다릴 수 있습니다
  5. 재시도할 수 있습니다
  6. 오랫동안 다른 goroutines을 블록할 수 있습니다

그것은 더 이상 뮤텍스가 아닙니다.

그것은 교통 정체입니다.

동일한 락을 필요로 하는 모든 고루틴은 전체 흐름이 끝날 때까지 기다려야 합니다. 따라서 서비스가 수백 또는 수천 개의 고루틴이 있더라도 시스템의 큰 부분이 순차적으로 작동됩니다.

위험한 부분은 CPU 사용량이 여전히 정상적으로 보이거나 심지어 낮아 보일 수 있다는 점이다. 메모리도 괜찮아 보일 수 있다. 하지만 지연 시간은 증가하고, 처리량은 감소하며, p95/p99 응답 시간은 불안정해진다.

이것이 이유는 락 경쟁이 기본 인프라 지표만으로는 때때로 인지하기 어려운 것 때문이다.


생산 형식 예시: Mutex 내부의 이메일

서비스가 사용자 상태를 업데이트하고 알림을 보내는 것을 상상해 보세요.

type Service struct {
    mu    sync.Mutex
    state map[int]string
}

func (s *Service) ProcessUsers(users []User) {
    s.mu.Lock()
    defer s.mu.Unlock()

    for _, user := range users {
        s.state[user.ID] = "processed"
        sendEmail(user) // slow network I/O inside the lock
    }
}

전체 화면 모드 입력 전체 화면 모드 종료

이 코드는 데이터 경쟁 관점에서 안전합니다.

하지만 성능 관점에서는 위험합니다.

뮤텍스는 가장 작은 가능한 공유 메모리 작업을 보호해야 합니다. 느린 외부 작업과 같은 것을 보호해서는 안 됩니다:

  • 이메일 전송
  • 다른 마이크로서비스 호출
  • 데이터베이스 쿼리
  • HTTP 요청
  • 파일 업로드
  • 느린 외부 시크를 위한 로깅
  • 제3자 API를 기다리는 중

메모리 업데이트는 나노초 또는 마이크로초가 걸릴 수 있습니다. 이메일 호출은 밀리초 또는 초가 걸릴 수 있습니다.

그 차이는 중요합니다.

sendEmail 가 실행 중에 잠이 잡혔을 때, s.mu 이 필요한 모든 고루틴은 네트워크 호출 뒤에 막혀 блок됩니다.

더 나은 버전은 공유 상태 변경을 느린 작업과 분리합니다.

func (s *Service) ProcessUsers(users []User) {
    emails := make([]User, 0, len(users))

    s.mu.Lock()
    for _, user := range users {
        s.state[user.ID] = "processed"
        emails = append(emails, user)
    }
    s.mu.Unlock()

    for _, user := range emails {
        sendEmail(user)
    }
}

전체 화면 모드를 입력합니다. 전체 화면 모드를 종료합니다.

이는 이미 더 나은 것입니다. 왜냐하면 잠은 공유 맵만 보호하기 때문입니다.

하지만 실제 생산 시스템에서는 느린 작업을 큐나 제한된 워커 풀로 밀어 넣는 것을 선호합니다:

func (s *Service) ProcessUsers(users []User, jobs chan<- EmailJob) {
    s.mu.Lock()
    for _, user := range users {
        s.state[user.ID] = "processed"
    }
    s.mu.Unlock()

    for _, user := range users {
        jobs <- EmailJob{UserID: user.ID, Email: user.Email}
    }
}

전체 화면 모드로 전환 전체 화면 모드 종료

이제 요청 경로가 직접 이메일 제공자 지연에 의존하지 않습니다.

이것이 정말 해결책입니다.

단순히 “고루틴을 사용하세요”만큼이지 않습니다.

수정 사항은 공유 메모리, 외부 I/O, 그리고 백프레셔 사이의 경계를 설계하는 것입니다.


뮤텍스는 나쁘지 않습니다. 큰 크리티컬 섹션은 나쁩니다.

나는 때때로 개발자들이 뮤텍스를 두려워하는 것을 보곤 합니다.

그것은 잘못된 교훈입니다.

sync.Mutex는 올바르게 사용될 때 간단하고 빠르며 완벽하게 괜찮습니다. 문제는 뮤텍스가 아니라, 크리티컬 섹션의 크기입니다.

이것을 기억하려고 노력합니다:

mu.Lock()
// only touch shared memory here
mu.Unlock()

전체 화면 모드로 전환 전체 화면 모드 종료

이것은 아닙니다:

mu.Lock()
// shared memory
// database call
// HTTP call
// email call
// JSON encoding
// logging
// metrics push
mu.Unlock()

전체 화면 모드로 전환 전체 화면 모드 종료

좋은 크리티컬 섹션은 지루해야 합니다.

보통 이 중 하나를 해야 합니다:

  • 공유 상태 읽기
  • 공유 상태 업데이트
  • 공유 상태를 지역 변수에 복사합니다
  • 포인터를 교환합니다
  • 카운터를 증가시킵니다
  • 보호된 슬라이스/맵에 추가합니다

그런 다음 잠금을 해제합니다.

그 후, 잠금 외부에서 비용이 많이 드는 작업을 수행합니다.


엔딩 아래: 뮤텍스가 당신에게 무엇을 제공하는지

상위 수준에서는 뮤텍스는 상호 배제를 제공합니다: 한 번에 하나의 고루틴만 보호된 섹션에 진입할 수 있습니다.

하지만 이는 메모리 순서 보장도 제공합니다.

Go의 메모리 모델에서는 해제 작업이 동일한 뮤텍스에 대한 나중의 잠금 작업보다 동기화됩니다. 실질적으로는 하나의 고루틴이 공유 데이터를 업데이트하고 해제하면, 나중에 동일한 뮤텍스를 잠금으로 걸어온 다른 고루틴이 업데이트를 안전하게 관찰할 수 있다는 의미입니다.

이것이 많은 개발자들이 잊어버리는 부분입니다.

뮤텍스는 단순히 "다른 고루틴을 블로킹하는 것"에만 관련되지 않습니다. 고루틴들 사이에 안전한 가시성 경계를 만드는 것에도 관련됩니다.

그 경계가 없으면 다른 고루틴들이 동시에 같은 메모리를 읽고 쓸 수 있으며, 이제 당신은 데이터 경쟁(data race)을 가지고 있습니다. 데이터 경쟁이 발생하면 당신의 프로그램은 더 이상 신뢰할 수 있는 방식으로 추론할 수 없습니다.

이것이 왜 “clever” lock-free 코드를 좋아하지 않는 이유입니다. 그 이유가 매우 강력하지 않다면요.

대부분의 백엔드 서비스는 쉬운 동시성이 필요하지 않습니다.

그들은 명확한 동시성이 필요합니다.


세마포어: 용량을 제어하는 것이 아니라 소유권을 제어합니다

뮤텍스는 보통 공유 메모리의 소유권과 관련이 있습니다.

세마포어는 용량과 관련이 있습니다.

예를 들어, 10,000명의 사용자를 처리하고 싶지만 동시에 10,000개의 이메일을 보내고 싶지 않다고 가정해 보세요.

초보적인 버전은 이렇게 할 수 있습니다.

for _, user := range users {
    go sendEmail(user)
}

전체 화면 모드를 입력합니다. 전체 화면 모드를 종료합니다.

이 위험하며, 이는 제한 없는 동시성을 생성합니다.

만약users은 10,000개의 항목을 가지고 있으며, 당신은 10,000개의 고루틴을 만듭니다. 각 고루틴이 네트워크 I/O를 수행하고, 연결을 열고, 메모리를 할당하고, 외부 제공자에게 기다리면, 이메일 제공자를 과부하시키기 전에 당신 자신의 서비스를 과부하시킬 수 있습니다.

간단한 세마포어 패턴이 이를 해결합니다.

sem := make(chan struct{}, 20) // allow only 20 concurrent email sends
var wg sync.WaitGroup

for _, user := range users {
    user := user

    sem <- struct{}{}
    wg.Add(1)

    go func() {
        defer wg.Done()
        defer func() { <-sem }()

        sendEmail(user)
    }()
}

wg.Wait()

전체 화면 모드로 진입 전체 화면 모드를 벗어남

현재 코드는 여전히 동시성을 사용하지만, 동시성은 제한적입니다.

그 하나의 세부 사항은 생산 환경에서 매우 중요합니다.

무제한 동시성은 확장성이 없습니다.

그것은 지연된 실패입니다.


생산 코드용 더 나은 워커 풀

세마포어 패턴은 유용하지만, 지속적으로 실행되는 서비스의 경우 워커 풀을 더 선호합니다.

type EmailJob struct {
    UserID int
    Email  string
}

func startEmailWorkers(ctx context.Context, workerCount int, jobs <-chan EmailJob) {
    var wg sync.WaitGroup

    for i := 0; i < workerCount; i++ {
        wg.Add(1)

        go func(workerID int) {
            defer wg.Done()

            for {
                select {
                case <-ctx.Done():
                    return

                case job, ok := <-jobs:
                    if !ok {
                        return
                    }

                    if err := sendEmailJob(ctx, job); err != nil {
                        // In real systems: log, retry, dead-letter, or expose metrics.
                        fmt.Printf("worker=%d failed to send email user_id=%d err=%v\n", workerID, job.UserID, err)
                    }
                }
            }
        }(i)
    }

    go func() {
        wg.Wait()
    }()
}

전체 화면 모드 입력 전체 화면 모드 종료

이렇게 하면 더 나은 운영 제어를 제공합니다:

  • 고정된 동시성
  • 쉬운 메트릭스
  • 쉬운 종료
  • 쉬운 재시도 전략
  • 쉬운 백프레셔
  • 쉬운 속도 제한

“나는 고루틴을 사용했다”와 “나는 동시성 시스템을 설계했다” 사이의 차이입니다.


고루틴 누수: 즉시 폭발하지 않는 버그

고루틴 누수는 Go에서 가장 흔한 생산 문제 중 하나입니다.

그들은 즉시 서비스가 중단하지 않기 때문에 위험합니다. 몇 시간 또는 며칠 동안 점차 더 나빠질 수 있습니다.

클래식한 예는 다음과 같습니다:

func process() error {
    ch := make(chan result)

    go func() {
        ch <- heavyComputation()
    }()

    select {
    case res := <-ch:
        return handle(res)

    case <-time.After(1 * time.Second):
        return errors.New("timeout")
    }
}

전체 화면 모드 입력 전체 화면 모드 종료

문제는 미묘합니다.

ch는 버퍼링되지 않습니다.

만약 타임아웃이 먼저 발생하면, process가 반환됩니다. 그 후에는 ch를 기다리는 수신자가 없습니다.

heavyComputation()이 끝나면, 고루틴은 ch으로 전송을 시도하고 영원히 블록됩니다.

그 고루틴이 지금 누수되었습니다.

한 개의 누수된 고루틴은 중요하지 않을 수 있습니다.

수천 개의 누수된 고루틴은 중요합니다.

더 안전한 버전은 버퍼링된 채널을 사용합니다.

func process() error {
    ch := make(chan result, 1)

    go func() {
        ch <- heavyComputation()
    }()

    select {
    case res := <-ch:
        return handle(res)

    case <-time.After(1 * time.Second):
        return errors.New("timeout")
    }
}

전체 화면 모드를 입력합니다. 전체 화면 모드를 종료합니다.

이는 타임아웃 후에 고루틴이 전송 중에 블로킹되는 것을 방지합니다.

하지만 실제 서비스에서는 맥락 기반 취소를 선호합니다:

func process(ctx context.Context) error {
    ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
    defer cancel()

    ch := make(chan result, 1)

    go func() {
        res := heavyComputation(ctx)

        select {
        case ch <- res:
        case <-ctx.Done():
        }
    }()

    select {
    case res := <-ch:
        return handle(res)

    case <-ctx.Done():
        return ctx.Err()
    }
}

전체 화면 모드로 전환 전체 화면 모드 종료

중요한 교훈:

각 고루틴은 종료 경로가 필요합니다.

고루틴이 어떻게 종료되는지 설명할 수 없다면, 누수가 발생할 가능성이 높습니다.


WaitGroup by Value: 작은 실수가 큰 영향을 미칩니다

코드 리뷰에서 매우 쉽게 놓칠 수 있는 오류는 다음과 같습니다:

func worker(wg sync.WaitGroup) { // wrong: copied by value
    defer wg.Done()

    // do work
}

전체 화면 모드로 전환 전체 화면 모드 종료

sync.WaitGroup는 처음 사용한 후에 복사해서는 안 됩니다.

값으로 전달하면 내부 상태를 복사합니다. 워커는 복사본에 대해 Done()를 호출하지만, 메인 고루틴이 기다리는 원본 WaitGroup에는 호출하지 않습니다.

그래서 데드락이 발생할 수 있습니다.

정확한 버전:

func worker(wg *sync.WaitGroup) {
    defer wg.Done()

    // do work
}

전체 화면 모드 입력 전체 화면 모드 종료

그리고 사용법:

var wg sync.WaitGroup

for i := 0; i < 10; i++ {
    wg.Add(1)
    go worker(&wg)
}

wg.Wait()

전체 화면 모드 입력 전체 화면 모드 종료

이 규칙은 다른 동기화 원시 자료들과도 적용됩니다.sync.Mutex.

초기 사용 후에는 그들을 복사하지 마세요.


루프 변수 함정

이전에는 가장 유명한 Go 동시성 버그 중 하나였습니다:

for _, user := range users {
    go func() {
        sendEmail(user)
    }()
}

전체 화면 모드로 전환 전체 화면 모드 종료

Go 버전과 맥락에 따라 루프 변수를 잘못 포착하면 고루틴이 잘못된 값을 사용할 수 있습니다.

방어 패턴은 여전히 간단하고 명확합니다:

for _, user := range users {
    user := user

    go func() {
        sendEmail(user)
    }()
}

전체 화면 모드로 전환 전체 화면 모드 종료

최신 Go 버전에서 개선되었지만, 여전히 생산 코드에서 이 스타일을 선호합니다. 변수의 소유권을 독자에게 명확하게 보여줍니다.

읽기 쉬운 동시성은 유지 관리하기 쉬운 동시성입니다.


Go에서 Lock Contention을 디버깅하는 방법

동시성 병목 현상을 의심할 때, 나는 추측을 시작하지 않습니다.

나는 측정을 시작합니다.

1. pprof 활성화

import _ "net/http/pprof"

func main() {
    go func() {
        log.Println(http.ListenAndServe("localhost:6060", nil))
    }()

    // start application
}

전체 화면 모드로 전환 전체 화면 모드 종료

그런 다음 프로파일 수집:

go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30

전체 화면 모드로 전환 전체 화면 모드 종료

뮤텍스 경쟁에 대해서는 뮤텍스 프로파일링을 활성화:

runtime.SetMutexProfileFraction(1)

전체 화면 모드로 전환 전체 화면 모드 종료

그런 다음 확인:

go tool pprof http://localhost:6060/debug/pprof/mutex

전체 화면 모드로 전환 전체 화면 모드 종료

2. 고루틴 수 확인

고루틴 수가 증가하는 것은 종종 블록된 고루틴이나 누수의 신호입니다.

fmt.Println("goroutines:", runtime.NumGoroutine())

전체 화면 모드로 전환 전체 화면 모드 종료

운영 환경에서는 이를 메트릭으로 노출하세요:

prometheus.NewGaugeFunc(
    prometheus.GaugeOpts{
        Name: "go_goroutines_current",
        Help: "Current number of goroutines.",
    },
    func() float64 {
        return float64(runtime.NumGoroutine())
    },
)

전체 화면 모드로 전환 전체 화면 모드 종료

3. 고루틴 스택 덤프

서비스가 멈춰 있을 때, 고루틴 덤프는 골드입니다.

curl http://localhost:6060/debug/pprof/goroutine?debug=2

전체 화면 모드 입력 전체 화면 모드 종료

같은 줄에서 블록된 많은 고루틴을 찾아보세요:

sync.(*Mutex).Lock
chan send
chan receive
net/http.(*Transport).RoundTrip

전체 화면 모드 입력 전체 화면 모드 종료

5,000개의 고루틴이 동일한 락이나 채널에 블락되어 있으면 병목 현상을 찾았습니다.

4. 테스트에서 경쟁 감지기 사용

go test -race ./...

전체 화면 모드 입력 전체 화면 모드 종료

경쟁 감지기는 비용이 들며, 일반적으로 생산 환경에서는 실행하지 않지만, CI 및 로컬 디버깅에서는 매우 유용합니다.


제 생산용 Go 동시성 실용 규칙

동시성 Go 코드를 작성하거나 검토할 때 따르려고 노력하는 규칙은 다음과 같습니다:

1. 락을 작게 유지

보호해야 할 데이터만 락을 걸으세요.

전체 요청 생애주기에 락을 걸지 마세요.

2. 결코 느린 I/O를 뮤텍스 안에 넣지 마세요

중요한 섹션 내에서 데이터베이스 호출, HTTP 호출, 이메일 발송, 파일 업로드 및 제3자 API 호출을 피하십시오.

3. 동시성 제한

무제한으로 고루틴을 생성하지 마십시오.

워커 풀, 세마포어, 큐 또는 속도 제한기를 사용하십시오.

4. 모든 고루틴에는 종료 경로가 필요합니다

context.Context를 사용하거나 채널 닫기 또는 명시적 취소를 사용하십시오.

5. 동기화 원리는 복사하지 마세요

*sync.WaitGroup*sync.Mutex 같은 원리는 포인터를 통해 공유할 때 사용하세요.

6. 최적화 전에 측정하세요

pprof를 사용하고, 런타임 메트릭스, 트레이스, 로그, 그리고 고루틴 덤프를 활용하세요.

추측은 디버깅이 아닙니다.

7. 재미없는 동시성을 선호하세요

가장 좋은 동시 코드는 보통 똑똑하지 않습니다.

그것은 명확하고 측정 가능하며 쉽게 중지할 수 있습니다.


마지막 생각

Go는 우리에게 강력한 동시성 도구를 제공하지만, 그것은 자동으로 좋은 동시 설계를 주지 않습니다.

Goroutine은 저렴하지만, 비용이 없지 않습니다.

Mutex는 빠르지만, 느린 작업을 주변에 두고 있으면 처리량을 파괴할 수 있습니다.

채널은 우아하지만, 아무도 받지 않으면 고루틴이 누수될 수 있습니다.

WaitGroup은 간단하지만, 복사하면 전체 흐름이 깨질 수 있습니다.

내에게 있어서, 경험 많은 Go 엔지니어링은 모든 동시성 원시형을 사용하는 것이 아니라, 사용하지 않을 때를 알고, 실제 경계가 어디인지, 그리고 시스템이 부하 하에서 어떻게 동작하는지를 알아야 합니다.

다음번에 이렇게 작성할 때:

mu.Lock()

전체 화면 모드 입력 전체 화면 모드 종료

이동하기 전에 한 가지 질문을 하세요:

나는 정확히 무엇을 보호하고 있으며, 이 잠을 얼마나 빨리 풀 수 있나요?

그 질문 하나가 당신의 서비스를 조용한 생산적 병목 현상에서 구출할 수 있습니다.


참조