Khóa học go

Concurrency trong Go Lang

0 phút đọc

Concurrency là một khái niệm quan trọng trong lập trình hiện đại, cho phép các chương trình thực hiện nhiều tác vụ cùng một lúc. Trong Go, concurrency được hỗ trợ mạnh mẽ thông qua các goroutines và channels. Bài viết này sẽ cung cấp một cái nhìn tổng quan chi tiết về concurrency trong Go, bao gồm cú pháp, cách sử dụng, và các ví dụ minh họa cụ thể.

Khái Niệm Concurrency

Concurrency là khả năng của một chương trình để thực hiện nhiều tác vụ cùng một lúc. Điều này có nghĩa là chương trình có thể xử lý nhiều công việc đồng thời mà không cần phải chờ đợi một công việc hoàn thành trước khi bắt đầu công việc khác. Concurrency khác với parallelism ở chỗ concurrency là về việc quản lý nhiều tác vụ cùng một lúc, trong khi parallelism là về việc thực hiện nhiều tác vụ cùng một lúc.

Goroutines

Goroutines là các hàm hoặc phương thức chạy đồng thời với các hàm hoặc phương thức khác. Goroutines có thể được coi là các luồng nhẹ (lightweight threads). Chi phí để tạo một goroutine rất nhỏ so với một luồng, do đó, các ứng dụng Go thường có hàng ngàn goroutines chạy đồng thời.

Cú Pháp Tạo Goroutine

Để tạo một goroutine, bạn sử dụng từ khóa go theo sau là lời gọi hàm.

Ví Dụ:

go Copy
package main

import (
    "fmt"
    "time"
)

func say(s string) {
    for i := 0; i < 5; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}

func main() {
    go say("world")
    say("hello")
}

Trong ví dụ này, hàm say được gọi như một goroutine với từ khóa go, cho phép nó chạy đồng thời với hàm say trong hàm main.

Channels

Channels là các cấu trúc dữ liệu được sử dụng để giao tiếp giữa các goroutines. Channels cho phép một goroutine gửi dữ liệu đến một goroutine khác một cách an toàn và đồng bộ.

Cú Pháp Tạo Channel

Để tạo một channel, bạn sử dụng hàm make với từ khóa chan.

Ví Dụ:

go Copy
package main

import "fmt"

func main() {
    messages := make(chan string)

    go func() { messages <- "ping" }()

    msg := <-messages
    fmt.Println(msg)
}

Trong ví dụ này, một channel messages được tạo và sử dụng để gửi và nhận thông điệp giữa các goroutines.

Buffered Channels

Buffered channels cho phép bạn chỉ định số lượng giá trị có thể được lưu trữ trong channel trước khi nó bị chặn.

Ví Dụ:

go Copy
package main

import "fmt"

func main() {
    messages := make(chan string, 2)

    messages <- "buffered"
    messages <- "channel"

    fmt.Println(<-messages)
    fmt.Println(<-messages)
}

Trong ví dụ này, channel messages có một bộ đệm với kích thước 2, cho phép lưu trữ hai giá trị trước khi nó bị chặn.

Select

Câu lệnh select trong Go cho phép một goroutine chờ nhiều hoạt động trên nhiều channels. Câu lệnh select sẽ chặn cho đến khi một trong các case có thể thực hiện được, sau đó nó sẽ thực hiện case đó.

Ví Dụ:

go Copy
package main

import (
    "fmt"
    "time"
)

func main() {
    c1 := make(chan string)
    c2 := make(chan string)

    go func() {
        time.Sleep(1 * time.Second)
        c1 <- "one"
    }()
    go func() {
        time.Sleep(2 * time.Second)
        c2 <- "two"
    }()

    for i := 0; i < 2; i++ {
        select {
        case msg1 := <-c1:
            fmt.Println("received", msg1)
        case msg2 := <-c2:
            fmt.Println("received", msg2)
        }
    }
}

Trong ví dụ này, câu lệnh select chờ cho đến khi một trong các channel c1 hoặc c2 nhận được một giá trị và sau đó thực hiện case tương ứng.

WaitGroup

WaitGroup là một công cụ đồng bộ hóa được sử dụng để chờ cho một tập hợp các goroutines hoàn thành. WaitGroup cung cấp ba phương thức chính: Add, Done, và Wait.

Ví Dụ:

go Copy
package main

import (
    "fmt"
    "sync"
    "time"
)

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done()
    fmt.Printf("Worker %d starting\n", id)
    time.Sleep(time.Second)
    fmt.Printf("Worker %d done\n", id)
}

func main() {
    var wg sync.WaitGroup

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

    wg.Wait()
}

Trong ví dụ này, WaitGroup được sử dụng để chờ cho tất cả các goroutines hoàn thành trước khi chương trình kết thúc.

Mutex

Mutex (mutual exclusion) là một công cụ đồng bộ hóa được sử dụng để bảo vệ truy cập vào các tài nguyên chia sẻ giữa các goroutines. Go cung cấp sync.Mutex để thực hiện điều này.

Ví Dụ:

go Copy
package main

import (
    "fmt"
    "sync"
)

type SafeCounter struct {
    v   map[string]int
    mux sync.Mutex
}

func (c *SafeCounter) Inc(key string) {
    c.mux.Lock()
    c.v[key]++
    c.mux.Unlock()
}

func (c *SafeCounter) Value(key string) int {
    c.mux.Lock()
    defer c.mux.Unlock()
    return c.v[key]
}

func main() {
    c := SafeCounter{v: make(map[string]int)}
    for i := 0; i < 1000; i++ {
        go c.Inc("somekey")
    }

    fmt.Println(c.Value("somekey"))
}

Trong ví dụ này, sync.Mutex được sử dụng để bảo vệ truy cập vào bản đồ v trong SafeCounter.

Thực Tiễn Tốt Nhất

  1. Sử Dụng Goroutines và Channels Đúng Cách: Goroutines và channels là các công cụ mạnh mẽ, nhưng chúng cần được sử dụng đúng cách để tránh các vấn đề như deadlock và race conditions.
  2. Sử Dụng select để Chờ Nhiều Channels: select là một công cụ mạnh mẽ để chờ nhiều channels và nên được sử dụng khi cần.
  3. Sử Dụng WaitGroup để Đồng Bộ Hóa Goroutines: WaitGroup là một công cụ hữu ích để chờ cho một tập hợp các goroutines hoàn thành.
  4. Bảo Vệ Tài Nguyên Chia Sẻ với Mutex: Khi làm việc với các tài nguyên chia sẻ, hãy sử dụng sync.Mutex để bảo vệ truy cập vào các tài nguyên đó.

Kết Luận

Concurrency là một khái niệm quan trọng trong Go, giúp các chương trình thực hiện nhiều tác vụ cùng một lúc một cách hiệu quả. Bài viết này đã cung cấp một cái nhìn tổng quan chi tiết về concurrency trong Go, bao gồm cú pháp, cách sử dụng, và các ví dụ minh họa cụ thể. Hiểu rõ về concurrency sẽ giúp bạn viết mã Go hiệu quả và dễ bảo trì hơn.

Avatar
Được viết bởi

Admin Team

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

Không có dữ liệu

Không có dữ liệu

Gợi ý bài viết
Không có dữ liệu

Không có dữ liệu

Bình luận

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

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