- Published on
การทำงานแบบ Concurrency ในภาษา Go
- Authors
- Name
- Somprasong Damyos
- @somprasongd
การทำงานแบบ Concurrency ในภาษา Go
ภาษา Go ได้รับการออกแบบมาเพื่อรองรับการประมวลผลแบบพร้อมกัน (Concurrency) อย่างมีประสิทธิภาพ ซึ่งเหมาะสำหรับงานที่ต้องทำหลาย ๆ อย่างพร้อมกัน เช่น การประมวลผลข้อมูลจำนวนมาก หรือการให้บริการ API ที่ต้องรองรับผู้ใช้จำนวนมาก
Go มีโครงสร้างหลักสำหรับ Concurrency สองอย่าง คือ:
- Goroutine: หน่วยประมวลผลที่ทำงานแบบขนาน
- Channel: กลไกสำหรับการสื่อสารระหว่าง Goroutines
ในบทความนี้ เราจะมาทำความเข้าใจแนวคิดเหล่านี้ พร้อมตัวอย่างการใช้งาน
Goroutine คืออะไร?
Goroutine เป็น Lightweight Thread ซึ่งใช้ทรัพยากรตำ่ ใน Go สามารถสร้าง Goroutine ได้ง่าย ๆ โดยการเพิ่มคีย์เวิร์ด go
หน้าคำสั่งที่ต้องการให้ทำงานแบบขนาน
ตัวอย่าง: การสร้าง Goroutine
package main
import (
"fmt"
"time"
)
func sayHello() {
for i := 1; i <= 5; i++ {
fmt.Println("Hello")
time.Sleep(1 * time.Second)
}
}
func main() {
go sayHello() // เรียกใช้ Goroutine
for i := 1; i <= 5; i++ {
fmt.Println("World")
time.Sleep(1 * time.Second)
}
}
ผลลัพธ์ที่ได้:
- คำว่า "Hello" และ "World" จะปรากฏสลับกัน เพราะทั้งสองฟังก์ชันทำงานพร้อมกัน
ทำไมต้องใช้ Goroutine?
- ประหยัดหน่วยความจำ (เบากว่า OS Thread)
- จัดการง่ายด้วย Go runtime
- เหมาะกับงานที่ต้องทำหลายอย่างพร้อมกัน เช่น I/O, Network Requests
Channel คืออะไร?
Channel เป็นตัวกลางสำหรับส่งข้อมูลระหว่าง Goroutine โดยสามารถใช้เพื่อการสื่อสารอย่างปลอดภัยในโปรแกรมที่ทำงานแบบขนาน
สร้าง Channel
ch := make(chan int) // สร้างช่องสัญญาณที่รับ-ส่งข้อมูลประเภท int
ส่งและรับข้อมูลผ่าน Channel
package main
import "fmt"
func main() {
ch := make(chan string)
go func() {
ch <- "Hello from Goroutine" // ส่งข้อมูลเข้า Channel
}()
msg := <-ch // รับข้อมูลจาก Channel
fmt.Println(msg)
}
ผลลัพธ์:
Hello from Goroutine
ตัวอย่างการใช้งาน Goroutine + Channel
ตัวอย่าง: คำนวณผลรวมแบบขนาน
package main
import "fmt"
func sum(numbers []int, result chan int) {
sum := 0
for _, n := range numbers {
sum += n
}
result <- sum // ส่งผลรวมเข้า Channel
}
func main() {
numbers := []int{1, 2, 3, 4, 5, 6}
ch1 := make(chan int)
ch2 := make(chan int)
go sum(numbers[:len(numbers)/2], ch1) // ครึ่งแรก
go sum(numbers[len(numbers)/2:], ch2) // ครึ่งหลัง
total := <-ch1 + <-ch2 // รวมผลลัพธ์จากทั้งสอง Goroutine
fmt.Println("Total Sum:", total)
}
ผลลัพธ์:
Total Sum: 21
การปิด Channel (Closing Channels)
เมื่อส่งข้อมูลเสร็จแล้ว สามารถปิด Channel ได้ด้วย close()
ตัวอย่าง: ปิด Channel
package main
import "fmt"
func main() {
ch := make(chan int, 3)
ch <- 1
ch <- 2
close(ch) // ปิด Channel
for num := range ch {
fmt.Println(num)
}
}
ผลลัพธ์:
1
2
select
เพื่อจัดการหลาย Channel
การใช้ select
ใช้สำหรับรอข้อมูลจากหลาย ๆ Channel พร้อมกัน
ตัวอย่าง: อ่านจากหลาย Channel
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(2 * time.Second)
ch1 <- "Message from ch1"
}()
go func() {
time.Sleep(1 * time.Second)
ch2 <- "Message from ch2"
}()
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
}
}
ผลลัพธ์:
Message from ch2
WaitGroup คืออะไร?
sync.WaitGroup
ใช้เพื่อรอให้ Goroutine ทั้งหมดทำงานเสร็จก่อนที่โปรแกรมจะสิ้นสุด
ตัวอย่าง: ใช้ WaitGroup
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // แจ้งว่า Goroutine นี้เสร็จแล้ว
fmt.Printf("Worker %d started\n", id)
time.Sleep(2 * time.Second)
fmt.Printf("Worker %d finished\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1)
go worker(i, &wg)
}
wg.Wait() // รอจนกว่าทุก Goroutine จะทำงานเสร็จ
fmt.Println("All workers finished")
}
Race Condition คืออะไร?
Race Condition เกิดขึ้นเมื่อหลาย Goroutine เข้าถึงและแก้ไขตัวแปรเดียวกันพร้อมกันโดยไม่มีการป้องกัน
ตัวอย่าง: Race Condition
package main
import (
"fmt"
"time"
)
var counter int
func increment() {
for i := 0; i < 1000; i++ {
counter++
}
}
func main() {
go increment()
go increment()
time.Sleep(1 * time.Second)
fmt.Println("Final Counter:", counter)
}
ป้องกัน Race Condition ด้วย Mutex
sync.Mutex
ใช้ล็อกตัวแปรเพื่อป้องกัน Race Condition
ตัวอย่าง: ใช้ Mutex ป้องกัน Race Condition
package main
import (
"fmt"
"sync"
)
var (
counter int
mu sync.Mutex
)
func increment() {
for i := 0; i < 1000; i++ {
mu.Lock()
counter++
mu.Unlock()
}
}
func main() {
var wg sync.WaitGroup
wg.Add(2)
go func() { defer wg.Done(); increment() }()
go func() { defer wg.Done(); increment() }()
wg.Wait()
fmt.Println("Final Counter:", counter)
}
สรุป
- Goroutine: ใช้สำหรับทำงานแบบขนาน
- Channel: ใช้ในการสื่อสารระหว่าง Goroutine
- WaitGroup: ใช้รอให้ Goroutine ทั้งหมดทำงานเสร็จ
- Race Condition: ปัญหาการเข้าถึงทรัพยากรพร้อมกัน
- Mutex: ใช้ล็อกเพื่อป้องกัน Race Condition