Menu
CoddyTech

sync.Mutex và sync.RWMutex

Thuộc phần Lập trình hướng đối tượng trong hành trình GO của Coddy. Bài học 63 trên 107.

Dù channel là cách Go ưu tiên để phối hợp các goroutine, đôi khi bạn cần trực tiếp bảo vệ dữ liệu dùng chung. Gói sync cung cấp mutex: các khóa đảm bảo mỗi lần chỉ một goroutine truy cập tài nguyên.

sync.Mutex có hai phương thức: Lock() và Unlock(). Khi một goroutine gọi Lock(), nó có quyền truy cập độc quyền. Các goroutine khác gọi Lock() sẽ bị chặn cho đến khi Unlock() được gọi:

type Counter struct {
    mu    sync.Mutex
    value int
}
func (c *Counter) Increment() {
    c.mu.Lock()
    c.value++
    c.mu.Unlock()
}
func (c *Counter) Value() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.value
}

Dùng defer c.mu.Unlock() giúp mở khóa ngay cả khi hàm trả về sớm hoặc gặp panic: đây là cách làm phổ biến và được khuyến nghị.

Khi lượt đọc diễn ra thường xuyên nhưng lượt ghi hiếm khi xảy ra, sync.RWMutex mang lại hiệu năng tốt hơn. Nó cho phép nhiều lượt đọc diễn ra đồng thời, nhưng lượt ghi cần quyền truy cập độc quyền:

type Cache struct {
    mu   sync.RWMutex
    data map[string]string
}
func (c *Cache) Get(key string) string {
    c.mu.RLock()         // Cho phép nhiều luồng đọc cùng lúc
    defer c.mu.RUnlock()
    return c.data[key]
}
func (c *Cache) Set(key, value string) {
    c.mu.Lock()          // Chỉ một luồng được truy cập để ghi
    defer c.mu.Unlock()
    c.data[key] = value
}

Dùng RLock()/RUnlock() cho các thao tác đọc và Lock()/Unlock() cho các thao tác ghi. Cách này cho phép nhiều thao tác đọc diễn ra đồng thời, đồng thời đảm bảo các thao tác ghi an toàn.

challenge icon

Thử thách

Dễ

Hãy xây dựng một hệ thống quản lý hàng tồn kho an toàn khi chạy đồng thời, theo dõi số lượng sản phẩm trong kho. Hệ thống sẽ xử lý an toàn các thao tác đọc và ghi đồng thời bằng mutex, đảm bảo tính toàn vẹn của dữ liệu khi nhiều thao tác diễn ra cùng lúc.

Bạn sẽ tổ chức code trong hai tệp:

  • inventory.go: Định nghĩa hệ thống quản lý hàng tồn kho an toàn khi chạy đồng thời.

    Tạo một struct Inventory lưu số lượng sản phẩm trong một map và dùng sync.RWMutex để bảo vệ quyền truy cập. Hệ thống quản lý hàng tồn kho cần hỗ trợ các thao tác sau:

    • NewInventory() *Inventory - Tạo một hệ thống quản lý hàng tồn kho mới với map đã được khởi tạo
    • AddStock(product string, quantity int) - Thêm số lượng vào kho của một sản phẩm (dùng khóa độc quyền vì thao tác này sửa đổi dữ liệu)
    • GetStock(product string) int - Trả về số lượng hiện có trong kho của một sản phẩm, hoặc 0 nếu không tìm thấy (dùng khóa đọc vì thao tác này chỉ đọc dữ liệu)
    • RemoveStock(product string, quantity int) bool - Trừ số lượng khỏi kho nếu kho còn đủ hàng. Trả về true nếu thành công, false nếu kho không đủ hàng (dùng khóa độc quyền)

    Nhớ dùng defer để mở khóa, đảm bảo các khóa luôn được giải phóng đúng cách.

  • main.go: Đọc các thao tác và minh họa hệ thống quản lý hàng tồn kho an toàn khi chạy đồng thời.

    Đọc số lượng thao tác, sau đó xử lý từng thao tác. Mỗi thao tác có một loại (add, get hoặc remove), tên sản phẩm và số lượng đối với thao tác add/remove.

    Với mỗi thao tác, in ra kết quả:

    • add: In ra Added [quantity] [product]
    • get: In ra [product]: [stock] in stock
    • remove: In ra Removed [quantity] [product] nếu thành công, hoặc Insufficient stock for [product] nếu không thành công

Đầu vào sẽ có định dạng như sau:

  • Dòng 1: Số lượng thao tác (số nguyên)
  • Các dòng tiếp theo: Với mỗi thao tác:
    • Loại thao tác (add, get hoặc remove)
    • Tên sản phẩm
    • Số lượng (chỉ có với thao tác add và remove)

Ví dụ, với đầu vào:

5
add
apples
50
get
apples
remove
apples
30
remove
apples
25
get
apples

Đầu ra của bạn sẽ là:

Added 50 apples
apples: 50 in stock
Removed 30 apples
Insufficient stock for apples
apples: 20 in stock

RWMutex cho phép nhiều lệnh gọi GetStock đọc dữ liệu đồng thời, trong khi AddStock và RemoveStock có quyền truy cập độc quyền khi sửa đổi hàng tồn kho.

Tự làm thử

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)
	
	// Đọc số lượng thao tác
	line, _ := reader.ReadString('\n')
	numOps, _ := strconv.Atoi(strings.TrimSpace(line))
	
	// Tạo kho hàng mới
	inventory := NewInventory()
	
	// Xử lý từng thao tác
	for i := 0; i < numOps; i++ {
		// Đọc loại thao tác
		opLine, _ := reader.ReadString('\n')
		opType := strings.TrimSpace(opLine)
		
		// Đọc tên sản phẩm
		productLine, _ := reader.ReadString('\n')
		product := strings.TrimSpace(productLine)
		
		// TODO: Xử lý từng loại thao tác (add, get, remove)
		// Với "add" và "remove", đọc số lượng từ đầu vào
		// Gọi phương thức (method) phù hợp của kho hàng
		// In ra kết quả theo mô tả thử thách
		
		switch opType {
		case "add":
			// TODO: Đọc số lượng, thêm hàng vào kho, in ra kết quả
			
		case "get":
			// TODO: Lấy số lượng hàng trong kho, in ra kết quả
			
		case "remove":
			// TODO: Đọc số lượng, lấy hàng khỏi kho, in ra kết quả phù hợp
			
		}
	}
}
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Trình biên dịch Go online