Khóa học go

Giới thiệu về Mutexes trong Go Lang

0 phút đọc

Mutex (viết tắt của "mutual exclusion") là một cơ chế đồng bộ hóa trong lập trình Go, cho phép kiểm soát truy cập vào các tài nguyên chia sẻ một cách an toàn. Khi nhiều goroutines cùng truy cập và thay đổi một tài nguyên chung, mutex đảm bảo rằng chỉ có một goroutine có thể truy cập tài nguyên đó tại một thời điểm, ngăn chặn các điều kiện đua (race conditions) và đảm bảo tính toàn vẹn của dữ liệu.

Tại sao cần Mutex?

Trong lập trình đồng thời, các goroutines có thể chạy song song và truy cập vào cùng một tài nguyên. Nếu không có cơ chế đồng bộ hóa, các goroutines có thể ghi đè lên dữ liệu của nhau, dẫn đến các kết quả không mong muốn và khó dự đoán. Mutex giúp giải quyết vấn đề này bằng cách khóa tài nguyên khi một goroutine đang sử dụng nó và mở khóa khi goroutine đó hoàn thành công việc của mình.

Cách sử dụng Mutex trong Go

Tạo và sử dụng Mutex

Mutex được cung cấp bởi gói sync trong Go. Để sử dụng mutex, bạn cần tạo một biến kiểu sync.Mutex và sử dụng các phương thức LockUnlock để kiểm soát truy cập vào tài nguyên chia sẻ.

Ví dụ cơ bản về Mutex:

package main

import (
    "fmt"
    "sync"
)

var counter int
var counterMutex sync.Mutex

func increment() {
    counterMutex.Lock()
    defer counterMutex.Unlock()
    counter++
}

func main() {
    var wg sync.WaitGroup

    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            increment()
        }()
    }

    wg.Wait()
    fmt.Println("Final counter value:", counter)
}

Trong ví dụ này, biến counter được chia sẻ giữa nhiều goroutines. Mutex counterMutex được sử dụng để bảo vệ biến này. Mỗi goroutine sẽ khóa mutex trước khi tăng giá trị của counter và mở khóa sau khi hoàn thành.

Các phương thức của Mutex

  • Lock(): Khóa mutex. Nếu mutex đã bị khóa, goroutine sẽ bị chặn cho đến khi mutex được mở khóa.
  • Unlock(): Mở khóa mutex. Nếu có goroutine khác đang chờ để khóa mutex, một trong số chúng sẽ được phép khóa mutex.

Tránh deadlock

Deadlock xảy ra khi hai hoặc nhiều goroutines chờ đợi lẫn nhau để mở khóa mutex, dẫn đến tình trạng không goroutine nào có thể tiến hành. Để tránh deadlock, bạn nên:

  1. Luôn mở khóa mutex sau khi hoàn thành công việc bằng cách sử dụng defer.
  2. Tránh giữ mutex trong thời gian dài.
  3. Tránh khóa nhiều mutex cùng một lúc. Nếu cần thiết, luôn khóa mutex theo cùng một thứ tự trong tất cả các goroutines.

Ví dụ về tránh deadlock:

package main

import (
    "fmt"
    "sync"
)

var mu1, mu2 sync.Mutex

func task1() {
    mu1.Lock()
    defer mu1.Unlock()
    mu2.Lock()
    defer mu2.Unlock()
    fmt.Println("Task 1 completed")
}

func task2() {
    mu2.Lock()
    defer mu2.Unlock()
    mu1.Lock()
    defer mu1.Unlock()
    fmt.Println("Task 2 completed")
}

func main() {
    go task1()
    go task2()

    // Đợi một chút để các goroutines hoàn thành
    select {}
}

Trong ví dụ này, task1task2 khóa các mutex theo thứ tự khác nhau, có thể dẫn đến deadlock. Để tránh deadlock, bạn nên đảm bảo rằng tất cả các goroutines khóa mutex theo cùng một thứ tự.

Các loại Mutex khác trong Go

RWMutex

RWMutex (Read-Write Mutex) là một loại mutex cho phép nhiều goroutines đọc dữ liệu đồng thời, nhưng chỉ cho phép một goroutine ghi dữ liệu tại một thời điểm. RWMutex có hai phương thức bổ sung: RLockRUnlock để khóa và mở khóa cho các hoạt động đọc.

Ví dụ về RWMutex:

package main

import (
    "fmt"
    "sync"
)

var counter int
var rwMutex sync.RWMutex

func readCounter() int {
    rwMutex.RLock()
    defer rwMutex.RUnlock()
    return counter
}

func writeCounter(value int) {
    rwMutex.Lock()
    defer rwMutex.Unlock()
    counter = value
}

func main() {
    var wg sync.WaitGroup

    // Tạo các goroutines để đọc dữ liệu
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            fmt.Println("Read counter:", readCounter())
        }()
    }

    // Tạo các goroutines để ghi dữ liệu
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(value int) {
            defer wg.Done()
            writeCounter(value)
        }(i)
    }

    wg.Wait()
}

Trong ví dụ này, rwMutex cho phép nhiều goroutines đọc giá trị của counter đồng thời, nhưng chỉ cho phép một goroutine ghi giá trị tại một thời điểm.

So sánh Mutex và Channel

Khi nào nên sử dụng Mutex?

  • Khi bạn cần kiểm soát truy cập vào một tài nguyên chia sẻ.
  • Khi không có sự giao tiếp giữa các goroutines.
  • Khi bạn cần bảo vệ các đoạn mã ngắn và đơn giản.

Khi nào nên sử dụng Channel?

  • Khi bạn cần giao tiếp và đồng bộ hóa giữa các goroutines.
  • Khi bạn cần truyền dữ liệu giữa các goroutines.
  • Khi bạn muốn tận dụng tính năng đồng bộ hóa tích hợp sẵn của channels.

Ví dụ về sử dụng Channel:

package main

import (
    "fmt"
    "sync"
)

func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
    defer wg.Done()
    for j := range jobs {
        fmt.Printf("Worker %d started job %d\n", id, j)
        results <- j * 2
    }
}

func main() {
    jobs := make(chan int, 100)
    results := make(chan int, 100)
    var wg sync.WaitGroup

    for w := 1; w <= 3; w++ {
        wg.Add(1)
        go worker(w, jobs, results, &wg)
    }

    for j := 1; j <= 5; j++ {
        jobs <- j
    }
    close(jobs)

    wg.Wait()
    close(results)

    for result := range results {
        fmt.Println("Result:", result)
    }
}

Trong ví dụ này, các goroutines giao tiếp với nhau thông qua channels để thực hiện các công việc và trả về kết quả.

Kết luận

Mutex là một công cụ mạnh mẽ trong Go để đảm bảo tính toàn vẹn của dữ liệu khi làm việc với các chương trình đồng thời. Bằng cách sử dụng mutex, bạn có thể ngăn chặn các điều kiện đua và đảm bảo rằng chỉ có một goroutine có thể truy cập vào tài nguyên chia sẻ tại một thời điểm. Tuy nhiên, mutex không phải là công cụ duy nhất để quản lý đồng bộ hóa trong Go. Channels cũng là một công cụ mạnh mẽ và linh hoạt, cho phép giao tiếp và đồng bộ hóa giữa các goroutines một cách an toàn và hiệu quả. Hiểu rõ khi nào nên sử dụng mutex và khi nào nên sử dụng channel sẽ giúp bạn viết mã Go hiệu quả và an toàn hơn.

Avatar
Được viết bởi

TechMely Team

Gợi ý câu hỏi phỏng vấn

entry

Dữ liệu được lưu trữ trong container sẽ bị mất trong những trường hợp nào?

entry

Tầm quan trọng của việc tạo mẫu trong thiết kế là gì?

entry

Các thành phần chính trong Microservices?

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào

Khoá học javascript từ cơ bản đến chuyên sâuYoutube Techmely