Go – Mutex

Mutex là hình thức ngắn gọn để loại trừ lẫn nhau. Mutex được sử dụng khi bạn không muốn một tài nguyên bị truy cập bởi nhiều Routine con trong cùng một thời điểm. Mutex có 2 phương thức Lock()Unlock(). Mutex được đặt trong package sync. Nên chúng ta phải import package sync. Các câu lệnh phải được thực hiên riêng rẽ lẫn nhau có thể được đặt trong mutex.Lock() hoặc mutex.Unlock(), do đó tránh được điều kiện Race.

mutex.Lock()  
x = x + 1  
mutex.Unlock()

Trong đoạn code bên trên x = x + 1 sẽ chỉ được chạy bởi một Goroutine tại bất cứ thời điểm nào do đó ngăn chặn được điều kiện Race.

Nếu một Goroutine nắm giữ lock (khoá) rồi và nếu một Goroutine mới đang cố gắng lấy được lock, thì Goroutine mới này sẽ đợi cho tợi khi Mutex được mở (unlock).

Hãy cùng xem ví dụ sau đây đếm số lần vòng lặp chạy. Trong chương trình này chúng ta mong đợi routine chạy vòng lặp 10 lần và count lưu trữ tổng số. Chúng ta gọi routine 3 lần nên tổng của nó nên là 30. count là biến global.

Đầu tiên, chạy chương trình không sử dụng Mutex

package main
import "fmt"
import "time"
import "strconv"
import "math/rand"
//Khai báo biến count được truy cập bởi tất cả các routine
var count = 0

// Sao chép count vào temp, thực hiện một vài xử lý (tăng dần) và lưu lại vào count
// tạm dừng một khoảng ngẫu nhiên được thêm vào giữa lúc đọc và ghi count
func process(n int) {
	// Vòng lặp tăng count 10 lần
	for i := 0; i < 10; i++ {
		time.Sleep(time.Duration(rand.Int31n(2)) * time.Second)
		temp := count
		temp++
		time.Sleep(time.Duration(rand.Int31n(2)) * time.Second)
		count = temp
	}
	fmt.Println("Count after i="+strconv.Itoa(n)+" Count:", strconv.Itoa(count))
}

func main() {
	// lặp gọi process() 3 lần
	for i := 1; i < 4; i++ {
		go process(i)
	}

	// Tạm dừng để đợi cho tất cả routine hoàn thành
	time.Sleep(25 * time.Second)
	fmt.Println("Final Count:", count)
}

Kết quả

Count after i=1 Count: 11
Count after i=3 Count: 12
Count after i=2 Count: 13
Final Count: 13

Kết quả chạy có thể khác bên trên nhưng sau cùng thì nó vẫn không là 30.

Ở đây điều đang xảy ra là 3 Goroutine  đang cố tăng vòng lặp và lưu vào trong biến count. Giả sử tại một thời điểm count đang là 5 và Goroutine1 sẽ tăng count lên 6, các bước xử lý như sau.

  1. Sao chép count vào temp
  2. Tăng giá trị temp
  3. Lưu temp ngược lại vào count

Giả sử ngay sau khi Goroutine1 thực hiện bước 3, Goroutine khác đang nắm giữ giá trị cũ là 3 theo các bước như trên và lưu lại giá trị là 4, nên kết quả bị sai.

Nào hãy thử chạy lại chương trình bên trên sử dụng Mutex

package main
import "fmt"
import "time"
import "sync"
import "strconv"
import "math/rand"

// Khai báo một instance mutex
var mu sync.Mutex

//Khai báo biến count được truy cập bởi tất cả các routine
var count = 0

// Sao chép count vào temp, thực hiện một vài xử lý (tăng dần) và lưu lại vào count 
// tạm dừng một khoảng ngẫu nhiên được thêm vào giữa lúc đọc và ghi count
func process(n int) {
	//Vòng lặp tăng count 10 lần
	for i := 0; i < 10; i++ {
		time.Sleep(time.Duration(rand.Int31n(2)) * time.Second)
		//Bắt đầu khoá ở đây
		mu.Lock()
		temp := count
		temp++
		time.Sleep(time.Duration(rand.Int31n(2)) * time.Second)
		count = temp
		// Mở khoá
		mu.Unlock()
	}
	fmt.Println("Count after i="+strconv.Itoa(n)+" Count:", strconv.Itoa(count))
}

func main() {
	//lặp gọi process() 3 lần
	for i := 1; i < 4; i++ {
		go process(i)
	}

	//Tạm dừng để đợi cho tất cả routine hoàn thành
	time.Sleep(25 * time.Second)
	fmt.Println("Final Count:", count)
}

Kết quả

Count after i=3 Count: 21
Count after i=2 Count: 28
Count after i=1 Count: 30
Final Count: 30

Như vậy chúng ta đã lấy được kết quả cuối cùng như mong đợi. Bởi vì các câu lệnh đọc, tăng, và ghi lại vào count được chạy trong Mutex.

You May Also Like

About the Author: Nguyen Dinh Thuc

Leave a Reply

Your email address will not be published.