惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

The GitHub Blog
The GitHub Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Microsoft Security Blog
Microsoft Security Blog
J
Java Code Geeks
S
SegmentFault 最新的问题
Apple Machine Learning Research
Apple Machine Learning Research
N
Netflix TechBlog - Medium
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园_首页
宝玉的分享
宝玉的分享
Google DeepMind News
Google DeepMind News
B
Blog RSS Feed
Hugging Face - Blog
Hugging Face - Blog
量子位
Blog — PlanetScale
Blog — PlanetScale
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
阮一峰的网络日志
阮一峰的网络日志
D
Docker
罗磊的独立博客
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
云风的 BLOG
云风的 BLOG
IT之家
IT之家
MyScale Blog
MyScale Blog
Microsoft Azure Blog
Microsoft Azure Blog

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
Concurrency ใน Go ตอนที่ 1: Basic & Synchronization
Perajit · 2026-05-01 · via DEV Community

ตอนแรกของบทความ "Concurrency ใน Go" เราจะว่ากันด้วยพื้นฐานเกี่ยวกับ concurrency และ synchronization

Concurrency vs Parallelism

ก่อนอื่นมาทำความเข้าใจสองคำนี้กันก่อน

Concurrency

  • นิยามคือ ความสามารถในการจัดการงานหลายอย่างในช่วงเวลาเดียวกัน
  • การทำงานแต่ละงานไม่จำเป็นต้องเกิดขึ้นพร้อมกัน แค่สลับงานไปมาก็ถือว่า concurrency
  • ลักษณะสำคัญคือ ช่วงเวลาเริ่มต้นของแต่ละงานมีการ overlap

Parallelism

  • นิยามคือ ความสามารถในการประมวลผลงานหลายอย่างพร้อมกัน
  • ต้องอาศัย hardware


ภาพประกอบโดย Gemini

หมายเหตุ: concurrency สามารถเป็น parallelism หรือไม่ก็ได้ ขึ้นอยู่กับ hardware mapping

ต่อให้ไม่ได้รัน parallel แต่การรัน concurrent ก็สามารถทำให้ทำงานได้เร็วขึ้นได้ เพราะปกติในงานทั่วไปมักจะต้องรออะไรบางอย่างเสมอ เช่น รอดึงค่าจาก memory หรือเก็บค่าลง memory ในระหว่างที่รอนี้ถ้าเราสามารถสลับไปรันอย่างอื่นได้ก็จะทำให้ทำงานโดยรวมได้มีประสิทธิภาพมากขึ้น

Concurreny และ Parallelism ใน Go

Go เป็นภาษาที่มี concurreny แบบ built-in คือไม่จำเป็นต้องใช้ library ช่วย
และสามารถทำงานเป็น parallelism ได้ด้วย โดยการกำหนดค่า GOMAXPROCS > 1

ตัวอย่างการกำหนดค่าจากในโค้ด

runtime.GOMAXPROCS(4)

Enter fullscreen mode Exit fullscreen mode

หรือจะตั้งค่าผ่าน environment variable ก็ได้

export GOMAXPROCS=4

Enter fullscreen mode Exit fullscreen mode

GOMAXPROCS คือการกำหนดจำนวน OS threads ที่สามารถประมวลผล Go code ได้พร้อมกันในช่วงเวลาหนึ่ง

ใน Go เวอร์ชั่น 1.5 เป็นต้นไป ค่าเริ่มต้นของ GOMAXPROCS เท่ากับจำนวน Logical CPUs ของเครื่องที่ใช้งาน ซึ่งเป็นค่าที่ดึงประสิทธิภาพของ hardware ออกมาได้สูงสุดอยู่แล้ว ไม่จำเป็นต้องแก้ไข ถ้าตั้งค่าสูงเกินไปจะทำให้เกิด context switching สูงขึ้น ทำให้ทำงานช้าลง

Goroutines

ปกติแล้ว concurrency ในภาษาอื่นจะใช้ Thread แต่สำหรับใน Go เราจะใช้สิ่งที่เรียกว่า Goroutine ซึ่งอาจจะเรียกว่าเป็น Lightweight Thread

แล้วมันต่างกันยังไง?

อย่างที่บอกว่า concurrency อาศัยการสลับงานไปมา หรือเรียกว่าการจัดตารางงาน (scheduling) และสำหรับ thread ตัวที่ทำหน้าที่จัดตารางงานก็คือ OS

แต่ใน Go เนี่ย goroutine จะรันอยู่ภายใน thread อีกที (สามารถมีได้ถึงหลายพันหรือหลายหมื่นภายใน thread เดียว และใช้ memory น้อยกว่า thread มาก) และจะอาศัย Go Runtime Scheduler ทำหน้าที่จัดตารางงาน

เมื่อเราสั่งรัน main() Go จะสร้าง goroutine ตัวแรกสุดที่เรียกว่า main routine ขึ้นมารัน ถ้าเราต้องการให้โปรแกรมทำงานแบบ concurrency เราก็สามารถรันงานที่ต้องการในอีก routine ได้ง่ายๆ แค่เขียน keyword go ข้างหน้าตอนเรียกฟังก์ชั่นเท่านั้นเอง

มาลองกันดูหน่อย

func main() {
  go fmt.Println("New Routine")
  fmt.Println("Main Routine")
}

Enter fullscreen mode Exit fullscreen mode

ผลลัพธ์ก็คือ เราจะเห็นแค่ข้อความเดียว...

Main Routine

Enter fullscreen mode Exit fullscreen mode

อ้าว?!

เรื่องของเรื่องก็คือ main() จบการทำงานไปก่อนที่ goroutine ใหม่จะทำงาน ดังนั้นเราต้องสั่งให้ Go รอ goroutine ใหม่ของเราก่อน

ซึ่งเราอาจจะสามารถสั่งให้ sleep รอก็ได้ เช่นรอไปเลย 100 ms แบบนี้

func main() {
  go fmt.Println("New Routine")
  time.Sleep(100 * time.Millisecond)
  fmt.Println("Main Routine")
}

Enter fullscreen mode Exit fullscreen mode

แต่อย่างที่รู้กันว่าไม่ควร เพราะจริงๆ เราบอกไม่ได้ว่าต้อง sleep นานแค่ไหน วิธีรออย่างถูกต้องก็คือรอจนกว่ามันจะเสร็จนั่นแหละ

Synchronization และ Sync Package

synchronization คือกลไกที่ใช้ควบคุมลำดับและการเข้าถึงทรัพยากรที่ใช้ร่วมกัน เพื่อป้องกันปัญหาที่เกิดจากการทำหลายงานชนกัน โดยเฉพาะปัญหา race condition

สำหรับใน Go เราจะใช้แพ็กเกจ sync สำหรับทำ synchronization ซึ่งมีตัวช่วยหลายอย่าง แต่เราจะหยิบมาพูดถึง 3 ตัว คือ sync.WaitGroup, sync.Once และ sync.Mutex

sync.WaitGroup

หนึ่งในวิธีที่จะบังคับให้ Go รอจนกว่างานใน routine ย่อยจะเสร็จได้ก็คือการใช้ sync.WaitGroup

หลักการการทำงานคือ

  • WaitGroup จะเก็บตัวนับ ซึ่งก็คือตัวเลขจำนวนงานที่ต้องรอ
  • ใช้ method Add() เพื่อเพิ่มตัวนับขึ้น (ใส่ตัวเลขจำนวนงานเท่าไหร่ก็ได้ ไม่จำเป็นต้องเพิ่มทีละ 1)
  • ใช้ method Done() บอกมันเมื่อเสร็จแต่ละงาน เพื่อให้เลขลดลง
  • ใช้ method Wait() เพื่อสั่งให้รอจนตัวนับเป็น 0 ซึ่งหมายความว่าไม่มีอะไรให้รอแล้ว

ตัวอย่างการใช้งาน

func foo(wg *sync.WaitGroup) {
  fmt.Println("New Routine")
  wg.Done() // บอกว่ารันจบแล้วงานนึง
}

func main() {
  var wg sync.WaitGroup
  wg.Add(2) // บอกว่ามีเพิ่ม 2 งาน
  go foo(&wg)
  go foo(&wg)
  wg.Wait() // บอกให้รอจนกว่างานทั้งหมดจะเสร็จ
  fmt.Println("Main Routine")
}

Enter fullscreen mode Exit fullscreen mode

หมายเหตุ: ต้องส่งค่า wg เป็น pointer เพื่อให้ทุกตัวลดตัวนับที่ตัวเดียวกัน ถ้าส่งแบบ value จะทำให้โปรแกรมค้างเพราะได้ wg เป็นค่า copy คนละตัวกัน

เราจะเห็นผลลัพธ์เป็น

New Routine
New Routine
Main Routine

Enter fullscreen mode Exit fullscreen mode

นั่นก็คือตอนนี้โปรแกรมของเราสามารถรัน goroutine ย่อยๆ ได้โดยไม่จบการทำงานไปก่อน

sync.Once

เป็นเครื่องมือที่ทำให้ฟังก์ชั่นที่ระบุถูกรันแค่ครั้งเดียวเท่านั้น ไม่ว่าจะมี goroutine เรียกใช้งานกี่ครั้งก็ตาม ใช้สำหรับการทำ initialization ที่ต้องทำแค่เพียงครั้งเดียว เช่น DB connection, setup state หรือทำ singleton pattern

func setup() {
  fmt.Println("Setup")
}

func main() {
  var once sync.Once
  var wg sync.WaitGroup

  for i := 0; i < 5; i++ {
    wg.Add(1)
    go func() {
      defer wg.Done()
      once.Do(setup)
    }()
  }

  wg.Wait()
}

Enter fullscreen mode Exit fullscreen mode

จากตัวอย่าง ถึงจะมีการเรียก once.Do(setup) จากหลาย goroutine (5 ตัว) แต่ setup() จะถูกเรียกทำงานแค่ครั้งเดียว

sync.Mutex

Mutex มาจาก Mutual Exclusion เป็นกลไกที่ใช้ป้องกันไม่ให้ goroutine หลายตัวเข้าถึง critical section ได้ในช่วงเวลาเดียวกัน

หลักการทำงานคือ

  • ใช้ Lock() เพื่อล็อกไม่ให้ goroutine อื่นเข้ามาใช้งาน
  • ใช้ Unlock() เพื่อปลดล็อกให้ goroutine อื่นเข้ามาใช้งานต่อได้
func main() {
  var mu sync.Mutex
  counter := 0
  var wg sync.WaitGroup

  for i := 0; i < 1000; i++ {
    wg.Add(1)
    go func() {
      defer wg.Done()

      mu.Lock()
      counter++
      mu.Unlock()
    }()
  }

  wg.Wait()
  fmt.Println(counter)
}

Enter fullscreen mode Exit fullscreen mode

critical section ซึ่งในที่นี้ก็คือ counter++ จะถูกล็อกเอาไว้ให้รันได้ทีละ goroutine ทำให้อัพเดทค่าได้อย่างถูกต้อง ถ้าไม่มีการล็อกไว้ผลลัพธ์ของ counter อาจไม่ใช่ 1000 (ถ้าใช้เยอะจะทำให้ performance แย่ลงเพราะต้องรอ)

หมายเหตุ: ปกติแล้วใน concurrency เราไม่นิยมแชร์ตัวแปรกลางและแก้ไขค่าร่วมกัน แต่มักจะใช้การส่งค่าผ่าน channel ซึ่งจะกล่าวถึงในบทความถัดไป