Skip to main content

Goroutines trong Go - Cách chạy nhiều tác vụ cùng lúc! 🚀

Chào mừng bạn đến với bài học về Goroutines trong Go! Trong bài học này, chúng ta sẽ tìm hiểu cách chạy nhiều tác vụ cùng lúc một cách hiệu quả.

Goroutine là gì? 🤔

Goroutine giống như một nhân viên làm việc độc lập - nó có thể:

  • Chạy cùng lúc với các goroutine khác
  • Tự động quản lý bởi Go runtime
  • Tiêu tốn ít tài nguyên hơn thread

💡 Ví dụ thực tế:

  • Giống như một nhà hàng có nhiều đầu bếp
  • Mỗi đầu bếp nấu một món riêng
  • Giúp phục vụ nhiều khách hàng cùng lúc

Khởi tạo Goroutine 🎯

1. Cách cơ bản

func main() {
go sayHello()
time.Sleep(time.Second)
}

func sayHello() {
fmt.Println("Xin chào từ goroutine!")
}

💡 Giải thích:

  • go: Từ khóa để chạy hàm đồng thời
  • time.Sleep: Đợi goroutine hoàn thành
  • Hàm chạy độc lập với main

2. Với hàm ẩn danh

func main() {
go func() {
fmt.Println("Xin chào từ goroutine ẩn danh!")
}()
time.Sleep(time.Second)
}

💡 Giải thích:

  • Tạo hàm ngay lập tức
  • Chạy như một goroutine
  • Tiện lợi cho các tác vụ ngắn

3. Với nhiều goroutines

func main() {
for i := 0; i < 5; i++ {
go func(id int) {
fmt.Printf("Goroutine %d\n", id)
}(i)
}
time.Sleep(time.Second)
}

💡 Giải thích:

  • Tạo nhiều goroutine cùng lúc
  • Truyền tham số vào hàm
  • Mỗi goroutine có ID riêng

WaitGroup - Đồng bộ Goroutines 🔄

1. Sử dụng WaitGroup

func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
fmt.Printf("Goroutine %d\n", id)
}(i)
}
wg.Wait()
}

💡 Giải thích:

  • wg.Add: Tăng số lượng goroutine cần đợi
  • wg.Done: Giảm số lượng khi hoàn thành
  • wg.Wait: Đợi tất cả hoàn thành

2. WaitGroup với xử lý lỗi

func xuLyDanhSach(danhSach []string) error {
var wg sync.WaitGroup
kenhLoi := make(chan error, len(danhSach))

for _, item := range danhSach {
wg.Add(1)
go func(item string) {
defer wg.Done()
if err := xuLyItem(item); err != nil {
kenhLoi <- err
}
}(item)
}

wg.Wait()
close(kenhLoi)

for err := range kenhLoi {
if err != nil {
return err
}
}
return nil
}

💡 Giải thích:

  • Thu thập lỗi từ các goroutine
  • Đợi tất cả hoàn thành
  • Trả về lỗi đầu tiên tìm thấy

Mutex - Bảo vệ Dữ liệu Chung 🔒

1. Sử dụng Mutex

type BoDem struct {
mu sync.Mutex
soLuong int
}

func (b *BoDem) Tang() {
b.mu.Lock()
defer b.mu.Unlock()
b.soLuong++
}

💡 Giải thích:

  • Lock: Khóa để chỉ một goroutine truy cập
  • Unlock: Mở khóa sau khi hoàn thành
  • defer: Đảm bảo mở khóa

2. RWMutex - Đọc/Ghi

type Cache struct {
mu sync.RWMutex
duLieu map[string]interface{}
}

func (c *Cache) Lay(key string) (interface{}, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
giaTri, ok := c.duLieu[key]
return giaTri, ok
}

func (c *Cache) Dat(key string, giaTri interface{}) {
c.mu.Lock()
defer c.mu.Unlock()
c.duLieu[key] = giaTri
}

💡 Giải thích:

  • RLock: Cho phép nhiều goroutine đọc
  • Lock: Chỉ một goroutine ghi
  • Hiệu quả hơn khi có nhiều đọc

Atomic Operations - Thao tác Nguyên tử ⚡

1. Sử dụng atomic

var boDem int64

func tang() {
atomic.AddInt64(&boDem, 1)
}

💡 Giải thích:

  • Thao tác an toàn với goroutines
  • Không cần Mutex
  • Hiệu suất cao hơn

2. Atomic với các kiểu khác

var (
boDem int64
co uint32
giaTri atomic.Value
)

💡 Giải thích:

  • Hỗ trợ nhiều kiểu dữ liệu
  • An toàn với goroutines
  • Dễ dàng sử dụng

Context - Quản lý Vòng đời 🎯

1. Sử dụng Context

func xuLyVoiContext(ctx context.Context, duLieu []byte) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
// Xử lý dữ liệu
return nil
}
}

💡 Giải thích:

  • Kiểm tra hủy bỏ
  • Dừng xử lý khi cần
  • Quản lý tài nguyên

2. Context với timeout

func xuLyVoiTimeout(duLieu []byte) error {
ctx, huy := context.WithTimeout(context.Background(), 5*time.Second)
defer huy()

return xuLyVoiContext(ctx, duLieu)
}

💡 Giải thích:

  • Giới hạn thời gian xử lý
  • Tự động hủy khi hết giờ
  • Tránh treo chương trình

Worker Pool - Quản lý Goroutines 🏭

1. Tạo Worker Pool

type WorkerPool struct {
congViec chan func()
wg sync.WaitGroup
}

func TaoWorkerPool(soWorker int) *WorkerPool {
pool := &WorkerPool{
congViec: make(chan func(), 100),
}

pool.wg.Add(soWorker)
for i := 0; i < soWorker; i++ {
go func() {
defer pool.wg.Done()
for job := range pool.congViec {
job()
}
}()
}

return pool
}

func (p *WorkerPool) GuiCongViec(congViec func()) {
p.congViec <- congViec
}

func (p *WorkerPool) Dung() {
close(p.congViec)
p.wg.Wait()
}

💡 Giải thích:

  • Quản lý số lượng goroutine
  • Tái sử dụng goroutine
  • Tránh tạo quá nhiều goroutine

Ví dụ Thực tế 🌟

1. Web Server với goroutines

func xuLyYeuCau(w http.ResponseWriter, r *http.Request) {
go func() {
// Xử lý yêu cầu bất đồng bộ
xuLyYeuCauChiTiet(r)
}()

w.Write([]byte("Đã nhận yêu cầu"))
}

💡 Giải thích:

  • Xử lý nhiều yêu cầu cùng lúc
  • Không chặn server
  • Hiệu suất cao

2. Giới hạn Tốc độ

type GioiHanTocDo struct {
ticker *time.Ticker
dung chan struct{}
}

func TaoGioiHanTocDo(tocDo time.Duration) *GioiHanTocDo {
return &GioiHanTocDo{
ticker: time.NewTicker(tocDo),
dung: make(chan struct{}),
}
}

func (g *GioiHanTocDo) Cho() {
<-g.ticker.C
}

func (g *GioiHanTocDo) Dung() {
g.ticker.Stop()
close(g.dung)
}

💡 Giải thích:

  • Kiểm soát tốc độ xử lý
  • Tránh quá tải
  • Bảo vệ tài nguyên

Best Practices (Cách sử dụng tốt nhất) ✅

  1. Sử dụng goroutines thông minh

    // ✅ Đúng
    go xuLyDuLieu()

    // ❌ Sai
    go func() {
    for {
    // Vòng lặp vô hạn
    }
    }()
  2. Đồng bộ hóa an toàn

    // ✅ Đúng
    var mu sync.Mutex
    mu.Lock()
    defer mu.Unlock()

    // ❌ Sai
    // Truy cập trực tiếp không có khóa
  3. Quản lý tài nguyên

    // ✅ Đúng
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    // ❌ Sai
    // Không hủy context
  4. Xử lý lỗi

    // ✅ Đúng
    go func() {
    if err := xuLy(); err != nil {
    kenhLoi <- err
    }
    }()

    // ❌ Sai
    go xuLy() // Không xử lý lỗi

Tiếp theo 🎯

Trong các bài học tiếp theo, chúng ta sẽ:

  • Tìm hiểu về channels
  • Học cách tối ưu hiệu suất
  • Khám phá các mẫu thiết kế concurrent
  • Thực hành với các dự án thực tế

💡 Lời khuyên: Hãy nghĩ về goroutines như những nhân viên làm việc độc lập. Khi quản lý tốt, họ sẽ giúp công việc hoàn thành nhanh chóng và hiệu quả!