ในบทความนี้จะพามารู้จักวิธีที่จะทำให้ 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)