ในบทความนี้จะพามารู้จักวิธีที่จะทำให้ Goroutines ใช้ข้อมูลตัวเดียวกัน แบบไม่ตีกันตายด้วย Mutex
เขียนโดย
Sirasit Boonklang – BorntoDev Co., Ltd.
Mutexes คือ ????
data structures ที่อยู่ใน sync package มันช่วยให้เราสามารถล็อคและปลดปลดล็อคข้อมูลเพื่อไม่ได้ให้เกิดความเสียหายจากการเข้าถึงข้อมูลพร้อมกัน เช่น การอ่านและเขียนในเวลาเดียวกันของคนละฟังก์ชัน ข้อมูลที่ได้อาจผิดพลาด
มาดูตัวอย่างกัน
มาดูกันดีกว่าครับว่าทำไมเราต้องล็อคข้อมูลไว้ กลัวหายหรอ แล้วใครที่จะมาเอาไป คำตอบก็จะมาจากการที่ go สามารถใช้ goroutine ทำงานพร้อม ๆ กันในแต่ละฟังก์ชันได้ทำให้อาจเกิดเหตุการฟังก์ชันนึงเขียนข้อมูล อีกฟังก์ชันที่อ่านข้อมูลเข้าถึงข้อมูลตัวเดียวกันในเวลาพร้อมกัน
เราจะมาทำความเข้าใจทีละส่วนกัน
ส่วนแรกจะเป็นการใช้ package main ปกติสำหรับ go แล้วก็ import package ที่จำเป็นนั้นก็คือ sync และ fmt
fmt ย่อมากจาก format เป็น package สำหรับ ใช้ในการแสดงผล
sync เป็น package สำหรับใช้ในการจัดการ Mutex
ส่วนต่อมาจะเป็นการสร้างตัวแปรที่จำเป็นต่อการใช้งาน Mutex
mutex sync.Mutex คือ สร้างตัวแปร mutex ขึ้นมา ที่มีชนิดเป็น sync.Mutex
balance int สร้างตัวแปร balance ขึ้นมา โดยกำหนดชนิดข้อมูลเป็น int
func init() {} เป็นฟังก์ชันเริ่มต้นจะทำงานเพียงครั้งเดียวเท่านั้น โดยจะทำงานก่อนที่ฟังก์ชัน main() จะทำงาน โดยจากตัวอย่างนี้เราจะกำหนดให้เงินคงเหลือของเราเริ่มต้นที่ 1000 ในตัวแปร balance
ต่อมาจะสร้างฟังก์ชัน func deposit() ขึ้นมาค่ามาหนึ่งตัวซึ่งเป็น int โดยการทำงานของฟังก์ชัน deposit() มีดังนี้
mutex.Lock() เป็นการเพื่อกำหนด critical section ของโค้ด
defer mutex.Unlock เพื่อปลดล็อค critical section ของโค้ด แล้วมีการใส่ defer หน้าคำสั่ง เพื่อให้คำสั่งนี้ทำงานเมื่อฟังก์ชัน deposit() ทำงานเสร็จสิ้น
แล้วก็แสดงผลค่าที่จะฝากเข้าบัญชี
balance += value บวกค่าที่จะฝากเข้าบัญชีเข้าไปในตัวแปร balance
fmt.Println(“”) แสดงผลบรรทัดว่าง
สุดท้ายจะเป็นฟังก์ชัน mian() ซึ่งเป็นฟังก์ชันการทำงานหลัก โดยเราจะสร้างตัวแปร wg ขึ้นมา โดยใช้คำสั่ง sync.WaitGroup สำหรับตัวแปร wg จะใช้ในการจัดการการทำงานของ Goroutine และจะเป็นตัวนับจำนวน Goroutine ที่กำลังทำงานอยู่ โดยจะเริ่มต้นทำงานด้วยค่า 0 และจะเพิ่มค่าขึ้นเมื่อมี Goroutine ทำงาน โดยใช้คำสั่ง wg.Add(1) จะลดค่าลงเมื่อ Goroutine ทำงานเสร็จสิ้น โดยใช้คำสั่ง wg.Done() จะทำการรอ Goroutine ทำงานเสร็จสิ้น โดยใช้คำสั่ง wg.Wait()
จากโค้ดการทำงานในฟังก์ชัน main()
var wg sync.WaitGroup เป็นการประกาศตัวแปร wg จะใช้ในการจัดการการทำงานของ Goroutine
wg.Add(2) จะเพิ่มค่าขึ้นเมื่อมี Goroutine ทำงานในที่นี้มี 2 goroutines
fmt.Println(“Start Balance : “, balance) แสดงค่าของตัวแปร balance ก่อนทำการฝากเงิน
แล้วเราก็ทำการสร้าง Goroutine ขึ้นมา โดยใช้คำสั่งดังนี้
จากตัวอย่าง Goroutine ที่ทำงานพร้อมกันคือฝากเงิน 200 และ 100 และเมื่อ routine ไหนทำงานเสร็จทำการลดค่าของตัวแปร wg ลง 1 ค่าจากคำสั่ง wg.Done()
หลังจากนั้นจะใช้คำสั่ง wg.Wait() เพื่อรอ Goroutine ทำงานเสร็จสิ้น แล้วทำการแสดงค่าของตัวแปร balance หลังทำการฝากเงินด้วยคำสั่ง fmt.Println(“Final Balance : “, balance)
สำหรับผลลัพธ์ที่ได้จะเป็นดังนี้
จะเห็นได้ว่าค่าในส่วนของ balance เมื่อฝาก 100 ไปค่าใน balance ก็จะยังอยู่ที่ 1000 เท่าเดิมในเวลาเดียวกันกับที่เพิ่มค่า 200 เข้าใน balance ทำให้การเข้าถึงข้อมูลในเวลาเดียวกันอาจจะเกิดข้อผิดพลาดขึ้นได้
Ref. Using a Mutex in Go (Golang) – with Examples (sohamkamani.com)