Concurrency ใน Go ตอนที่ 1: Basic & Synchronization

Published: (April 30, 2026 at 09:25 PM EDT)
2 min read
Source: Dev.to

Source: Dev.to

พื้นฐาน Concurrency และ Synchronization

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

Concurrency

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

Parallelism

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

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

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

Concurrency ใน Go

Go มี concurrency แบบ built‑in ไม่ต้องพึ่ง library ภายนอก

GOMAXPROCS

กำหนดจำนวน OS threads ที่สามารถประมวลผล Go code ได้พร้อมกัน

runtime.GOMAXPROCS(4)   // ตั้งค่าจากโค้ด

หรือผ่าน environment variable

export GOMAXPROCS=4

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

Goroutine

Go ใช้ goroutine ซึ่งเป็น lightweight thread (อาจมีหลายพันหรือหลายหมื่นภายใน thread เดียว) และใช้ Go Runtime Scheduler จัดตารางงาน

เมื่อโปรแกรมเริ่ม main() Go จะสร้าง goroutine ตัวแรกที่เรียกว่า main routine หากต้องการทำงานแบบ concurrent เพียงใส่คีย์เวิร์ด go ข้างหน้าการเรียกฟังก์ชัน

ตัวอย่างพื้นฐาน

package main

import (
	"fmt"
	"time"
)

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

ผลลัพธ์จะเห็นเพียง

Main Routine

เพราะ main() จบก่อนที่ goroutine ใหม่จะทำงาน

วิธีรอ goroutine อย่างถูกต้อง

การใช้ time.Sleep ไม่แนะนำ เพราะไม่รู้ว่าจะต้องรอกี่เวลา วิธีที่เหมาะสมคือใช้กลไก synchronization เช่น sync.WaitGroup

Synchronization ด้วยแพ็กเกจ sync

sync.WaitGroup

WaitGroup เก็บตัวนับจำนวนงานที่ต้องรอ

  • Add(n) – เพิ่มตัวนับ
  • Done() – ลดตัวนับเมื่องานเสร็จ
  • Wait() – รอจนตัวนับเป็น 0

ตัวอย่าง

package main

import (
	"fmt"
	"sync"
)

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")
}

ผลลัพธ์

New Routine
New Routine
Main Routine

sync.Once

Once ทำให้ฟังก์ชันที่ระบุรันแค่ครั้งเดียว แม้จะถูกเรียกจากหลาย goroutine

ตัวอย่าง

package main

import (
	"fmt"
	"sync"
)

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

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

	for i := 0; i  // (โค้ดตัวอย่างนี้ยังไม่สมบูรณ์ในต้นฉบับ)
}

หมายเหตุ: ใน Go เรามักหลีกเลี่ยงการแชร์ตัวแปรกลางและแก้ไขค่าร่วมกันโดยตรง แทนที่จะใช้ channel ซึ่งจะอธิบายในบทความถัดไป.

0 views
Back to Blog

Related posts

Read more »

Go do zero: var vs :=

Go tem duas formas de declarar variáveis: var e :=. Elas existem por motivos diferentes e têm regras distintas. Saber quando usar cada uma evita erros bobos e c...