- vừa được xem lúc

Tìm hiểu lập trình concurrency trong Golang

0 0 2

Người đăng: Quan Troy

Theo Viblo Asia

Giới thiệu

Concurrency là một khái niệm quan trọng trong lập trình, và Golang (Go) đã đặt ra một tiêu chuẩn mới về cách xử lý đồng thời. Bài viết này sẽ cùng bạn khám phá tại sao nó là một phần quan trọng của ngôn ngữ này và làm thế nào nó giúp xây dựng ứng dụng hiệu quả, mạnh mẽ.

Concurrency là gì

1. Lập trình đồng thời (Concurrency) là khả năng phân chia và điều phối nhiều tác vụ khác nhau trong cùng một khoảng thời gian, việc chuyển qua chuyển lại tác vụ rất nhanh nhưng tại một thời điểm chỉ có thể xử lý một tác vụ. Ở trong Go mỗi task vụ này là 1 goroutine.

Goroutines là gì?

Goroutines là các luồng thực thi nhẹ lightweight execution thread do khởi tạo tốn rất ít bộ nhớ stack. Nó được quản lý bởi hệ điều hành, cho phép chúng ta thực hiện các tác vụ đồng thời một cách hiệu quả. Hiểu đơn giản, Goroutines bản chất là các hàm (function) được thực thi một các độc lập và đồng thời với nhau.

  • Một chương trình Go có thể chứa hàng ngàn Goroutines xử lý đồng thời
  • Trong mỗi chương trình Golang, luôn có một Goroutine chính, gọi là main Goroutine. Nếu thằng chính này dừng lại, thì tất cả những thằng khác (Goroutines) trong chương trình cũng sẽ dừng lại, không được chạy.

Việc khai báo Goroutines trong Golang thì cực kỳ đơn giản, không cần phải import bất kì package nào cả. Bạn chỉ cần thêm cái từ khóa go trước lời gọi hàm mà bạn muốn chúng chạy đồng thời thôi.

func quan(){
// code
} // goroutines
// only using go keyword
go quan()

Để hiểu rõ hơn về Goroutines hãy cùng làm một ví dụ nữa

package main
import ( "fmt"
) func showName(s string) { for i := 0; i < 3; i++ { fmt.Println(s) }
} func main() { // 2 goroutine go showName("Quân") go showName("Troy")
}

Ouput:


Như ở ví dụ này, chúng ta mong muốn "Quân" và "Troy" đều phải được in ra 3 lần, nhưng ouput lại không có gì cả. Tại sao lại như vậy? Bởi vì, do cả 2 hàm showName có dùng từ khóa go nên nó là goroutine và khi hàm main được chạy nó thấy goroutine sẽ đẩy vào Local Run Queue và Go Runtime sẽ tiếp tục chạy code bên dưới ở hàm main, nhưng không còn code nào để chạy cả nên hàm main sẽ dừng lại. Như có viết ở trên khi màn main dừng thì tất cả goroutine khác đều không được chạy.

Vậy làm thế nào để hàm main đợi 2 goroutine chạy xong mới dừng lại? Đơn giản nhất là chúng ta sẽ tạm dừng hàm main 1 khoảng thời gian để 2 goroutine kia được chạy

package main
import ( "fmt" "time"
) func showName(s string) { for i := 0; i < 3; i++ { fmt.Println(s) }
} func main() { go showName("Quân") go showName("Troy") // Pause the execution for 1 second time.Sleep(1 * time.Second)
}

Ouput:

Troy
Troy
Troy
Quân
Quân
Quân

Hàm time.Sleep(1 * time.Second) sẽ tạm dừng hàm main trong 1s. Nên 2 goroutine đã có thời gian để chạy và in ra được kết quả như chúng ta mong muốn. Đương nhiên, trong thực tế chúng ra không để biết goroutine cần bao nhiêu thời gian để để chúng ta Sleep tương ứng, mà chúng ta sẽ cần tìm hiểu thêm về WaitGroup, Channel để hàm main đợi goroutine thực sự chạy xong mới dừng lại.

WaitGroup

Vẫn dùng ví dụ trên nhưng chúng ta sẽ sửa một chút, không dùng Sleep nữa

package main
import ( "fmt" "sync"
) func sayName(s string, wg *sync.WaitGroup) { // Báo cho hàm main biết là đã chạy xong  defer wg.Done() for i := 0; i < 3; i++ { fmt.Println(s) }
} func main() { // Khởi tạo một biến kiểu sync.WaitGroup var wg sync.WaitGroup // thông báo thêm 2 goroutine cần phải đợi wg.Add(2) go sayName("Quan", &wg) go sayName("Troy", &wg) // Đợi cho tất cả các goroutine chạy xong wg.Wait() fmt.Println("Application end")
}

Ouput:

Troy
Troy
Troy
Quan
Quan
Quan
Application end

Vẫn ra ouput mong muốn mà không cần dùng Sleep. Mình sẽ giải thích 1 chút về cách hoạt động của WaitGroup trong ví dụ ở trên. Đầu tiên, ta khởi tạo một biến thuộc kiểu sync.WaitGroup (sync là một package trong Go). Sau đó, ta thông báo cho WaitGroup biết có bao nhiêu Goroutine cần phải đợi bằng cách sử dụng phương thức Add(n) với n là số lượng Goroutine. Trong trường hợp này, chúng ta có 2 Goroutine nên sử dụng wg.Add(2). Tiếp theo, khi một Goroutine hoàn thành nhiệm vụ của nó, ta thông báo cho hàm main biết bằng cách gọi phương thức wg.Done(). Từ khóa defer được sử dụng để đảm bảo rằng một hàm sẽ được gọi vào cuối cùng của hàm chứa nó, trong trường hợp này là wg.Done(). Cuối cùng, wg.Wait() sẽ lock hàm main lại đợi goroutine báo đã chạy xong mới unlock và thực hiện in ra dòng "Application end".

Có một lưu ý là phải truyền biến sync.WaitGroup vào goroutine theo dạng tham trị &wg nếu chỉ truyền bình thường wg thì trong goroutine sẽ copy lại biến wg điều này dẫn đến biến wg ở trong goroutine và hàm main đang không phải là một và logic code sẽ không chạy đúng nữa.

Channel

Trong các ví dụ trên, chúng ta đã thấy các Goroutines chạy độc lập với nhau. Tuy nhiên, liệu có cách nào để chúng giao tiếp với nhau hay không? Câu trả lời là có, và đó là thông qua Channel.

  • Mặc định, Channel là một kênh giao tiếp 2 chiều, có nghĩa là nó có thể được sử dụng để gửi và nhận dữ liệu.
  • Channel này giúp các Goroutines có thể gửi và nhận dữ liệu cho nhau một cách an toàn thông qua cơ chế lock-free. Điều này đảm bảo rằng dữ liệu không bị mất hoặc xảy ra xung đột khi nhiều Goroutines cố gắng truy cập cùng một lúc.

Ví dụ đơn giản về Channel

package main
import "fmt" func sayName(s string, ch chan string) { result := "" for i := 0; i < 3; i++ { result += s + " is my name, " } // gửi giá trị vào channel ch <- result
} func main() { // tạo channel có thể chứa string ch := make(chan string) go sayName("Quan", ch) // lấy giá trị từ channel fmt.Println(<-ch) fmt.Println("Application end")
}

Ouput:

Quan is my name, Quan is my name, Quan is my name, Application end

Trong đoạn code trên chúng ta có thể thấy việc giao tiếp cơ bản giữa goroutine sayName() và main goroutine. sayName() đã gửi dữ liệu vào vào Channel ch <- result và main goroutines đã có thể nhận và sử dụng nó. Toán tử <- đóng vai trò nhưng hướng chỉ dữ liệu sẽ đi từ đâu đến đâu.

Kết

Trong bài viết, chúng ta đã tìm hiểu cách sử dụng Goroutines. Từ cách khai báo và quản lý Goroutines đến cách đợi chúng hoàn thành, mỗi ví dụ đã giúp chúng ta hiểu rõ hơn về tính năng quan trọng này trong Go. Nhờ vào Goroutines, chúng ta có thể xây dựng các ứng dụng mạnh mẽ và linh hoạt, đồng thời tận dụng tối đa tài nguyên máy tính. Điều này làm tăng hiệu suất và khả năng mở rộng của ứng dụng.

Bài viết là sự tổng hợp của mình trong quá trình học Go, nếu có sai xót hay thắc mắc gì mong các bạn comment bên dưới. Cảm ơn bạn đã đọc.

Tài liệu tham khảo

https://www.youtube.com/playlist?list=PLlahAO-uyDzIVzBvRKwKUDjj2Iaq-5W9l https://200lab.io/blog/goroutines-la-gi/
https://go.dev/tour/concurrency/1

Bình luận

Bài viết tương tự

- vừa được xem lúc

gRPC - Nó là gì và có nên sử dụng hay không?

Nhân một ngày rảnh rỗi, mình ngồi đọc lại RPC cũng như gRPC viết lại để nhớ lâu hơn. Vấn đề là gì và tại sao cần nó .

0 0 112

- vừa được xem lúc

Embedded Template in Go

Getting Start. Part of developing a web application usually revolves around working with HTML as user interface.

0 0 40

- vừa được xem lúc

Tạo Resful API đơn giản với Echo framework và MySQL

1. Giới thiệu.

0 0 44

- vừa được xem lúc

Sử dụng goquery trong golang để crawler thông tin các website Việt Nam bị deface trên mirror-h.org

. Trong bài viết này, mình sẽ cùng mọi người khám phá một package thu thập dữ liệu có tên là goquery của golang. Mục tiêu chính của chương trình crawler này sẽ là lấy thông tin các website Việt Nam bị deface (là tấn công, phá hoại website, làm thay đổi giao diện hiển thị của một trang web, khi người

0 0 218

- vừa được xem lúc

Tạo ứng dụng craw dữ liệu bing với Golang, Mysql driver

Chào mọi người . Lâu lâu ta lại gặp nhau 1 lần, để tiếp tục series chia sẻ kiến thức về tech, hôm nay mình sẽ tìm hiểu và chia sẻ về 1 ngôn ngữ đang khá hot trong cộng đồng IT đó là Golang.

0 0 59

- vừa được xem lúc

Golang: Rest api and routing using MUX

Routing with MUX. Let's create a simple CRUD api for a blog site. # All . GET articles/ .

0 0 39