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ờitime.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 đợiwg.Done
: Giảm số lượng khi hoàn thànhwg.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ậpUnlock
: Mở khóa sau khi hoàn thànhdefer
: Đả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 đọcLock
: 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) ✅
-
Sử dụng goroutines thông minh
// ✅ Đúng
go xuLyDuLieu()
// ❌ Sai
go func() {
for {
// Vòng lặp vô hạn
}
}() -
Đồ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 -
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 -
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ả!