Cơ bản về goroutine
Thuộc phần Lập trình hướng đối tượng trong hành trình GO của Coddy. Bài học 59 trên 107.
goroutine là một luồng thực thi nhẹ do môi trường chạy Go quản lý. Khác với các luồng truyền thống tiêu tốn nhiều bộ nhớ, goroutine bắt đầu với chỉ vài kilobyte không gian ngăn xếp, cho phép bạn chạy đồng thời hàng nghìn goroutine.
Để bắt đầu một goroutine, bạn chỉ cần đặt từ khóa go trước lời gọi hàm:
func sayHello(name string) {
fmt.Println("Hello,", name)
}
func main() {
go sayHello("Alice") // chạy đồng thời
go sayHello("Bob") // chạy đồng thời
time.Sleep(100 * time.Millisecond)
fmt.Println("Done")
}Khi go sayHello("Alice") chạy, chương trình không chờ sayHello hoàn tất. Thay vào đó, chương trình lập tức tiếp tục sang dòng tiếp theo trong khi sayHello chạy ở chế độ nền. Vì vậy, chúng ta cần time.Sleep. Nếu không có nó, main sẽ thoát trước khi các goroutine hoàn tất.
Bạn cũng có thể khởi chạy goroutine bằng các hàm ẩn danh (anonymous function):
go func(msg string) {
fmt.Println(msg)
}("Hello from goroutine")Bản thân hàm main chạy trong một goroutine. Khi hàm này trả về, tất cả goroutine khác đều bị kết thúc ngay lập tức. Chúng không có cơ hội hoàn thành. Dùng time.Sleep là giải pháp tạm thời; trong các bài học tiếp theo, bạn sẽ học các kỹ thuật đồng bộ phù hợp bằng channel và wait group.
Thử thách
DễHãy xây dựng một trình chạy tác vụ để thực thi đồng thời nhiều tác vụ bằng goroutine. Bạn sẽ tạo một hệ thống đơn giản, trong đó các tác vụ chạy ở nền trong khi chương trình chính tiếp tục hoạt động, qua đó minh họa cách goroutine cho phép thực thi đồng thời.
Bạn sẽ sắp xếp code trong hai tệp:
tasks.go: Định nghĩa logic thực thi tác vụ.Tạo một struct
Taskvới các trườngName(string) vàDuration(int, biểu thị số mili giây).Cài đặt phương thức
Run()choTaskđể mô phỏng công việc bằng cách tạm dừng trong khoảng thời gian của tác vụ, sau đó in ra:Task [Name] completedTạo hàm
RunAllTasks(tasks []Task)để chạy phương thứcRun()của từng tác vụ dưới dạng goroutine. Sau khi khởi chạy tất cả goroutine, hàm nên tạm dừng trong 500 mili giây để chúng hoàn tất, rồi in ra:All tasks launchedmain.go: Đọc thông tin tác vụ và điều phối quá trình thực thi đồng thời.Đọc số lượng tác vụ, sau đó đọc tên và thời lượng của từng tác vụ. Tạo các tác vụ và truyền chúng cho
RunAllTasks. Khi hàm này trả về, in ra:Main function done
Đầu vào sẽ có dạng sau:
- Dòng 1: Số lượng tác vụ (số nguyên)
- Các dòng tiếp theo: Với mỗi tác vụ, hai dòng gồm tên tác vụ, rồi thời lượng tính bằng mili giây
Ví dụ, với đầu vào:
3
Download
100
Process
50
Upload
75Đầu ra cần hiển thị các tác vụ hoàn tất (thứ tự có thể thay đổi do thực thi đồng thời), sau đó là các thông báo hoàn tất:
Task Process completed
Task Upload completed
Task Download completed
All tasks launched
Main function doneLưu ý: Vì goroutine chạy đồng thời, các tác vụ có thời lượng ngắn hơn thường hoàn tất trước. Thứ tự hoàn tất phụ thuộc vào thời lượng của chúng: tác vụ ngắn hơn hoàn tất trước tác vụ dài hơn.
Dùng time.Sleep cùng với time.Duration và time.Millisecond để tạo khoảng dừng. Hãy nhớ rằng nếu không có lệnh tạm dừng trong RunAllTasks, hàm sẽ trả về trước khi các goroutine hoàn tất công việc.
Tự làm thử
package main
import (
"fmt"
)
func main() {
// Đọc số lượng tác vụ
var numTasks int
fmt.Scanln(&numTasks)
// TODO: Tạo một slice để lưu các tác vụ
// TODO: Đọc tên và thời lượng của từng tác vụ
// Với mỗi tác vụ:
// - Đọc tên tác vụ
// - Đọc thời lượng (dưới dạng số nguyên)
// - Tạo một Task và thêm vào slice của bạn
// TODO: Gọi RunAllTasks với slice chứa các tác vụ
// TODO: In ra "Main function done"
}
Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.
Tất cả bài học trong Lập trình hướng đối tượng
1Nền tảng OOP trong Go
Tệp bên ngoàiWorkspace và module trong GoPackage và importTên được export và không được exportGiới thiệu về OOP trong GoStruct như lớpĐịnh nghĩa phương thức cho structReceiver kiểu con trỏ và kiểu giá trịKhởi tạo structHàm khởi tạoÔn tập: máy tính đơn giản4Interface
Giới thiệu về interfaceTriển khai ngầm địnhInterface như một hợp đồngInterface rỗng (any)Khẳng định kiểuswitch kiểuKết hợp interfaceInterface Stringer và ErrorÔn tập: Máy tính hình học7Tính đóng gói
Trường được xuất và không được xuấtĐóng gói ở cấp packagePhương thức getter và setterẨn thông tin trong GoÔn tập: hồ sơ học sinh10Generics (Go 1.18 trở lên)
Giới thiệu về GenericsTham số kiểuRàng buộc kiểuStruct tổng quátGiải pháp thay thế cho phương thức tổng quátÔn tập: tập hợp tổng quát13Các mẫu thiết kế phần 1
Giới thiệu về mẫu thiết kếMẫu SingletonMẫu FactoryMẫu Abstract FactoryMẫu ObserverMẫu Strategy2Tìm hiểu chuyên sâu về kiểu dữ liệu và struct
Kiểu cơ bản và kiểu tổng hợpĐịnh nghĩa kiểu dữ liệu tùy chỉnhThẻ structStruct ẩn danhStruct lồng nhauGiá trị zero và giá trị mặc địnhÔn tập: sổ liên lạc5Ưu tiên kết hợp hơn kế thừa
Vì sao Go không có kế thừaNhập môn nhúng structTự động nâng phương thứcNhúng nhiều structNhúng và kết tậpChe khuất phương thức được nhúngÔn tập: hệ phân cấp nhân viên8Xử lý lỗi và lập trình hướng đối tượng
Giao diện errorCác kiểu lỗi tùy chỉnhBọc lỗi với fmt.ErrorfLỗi sentinelerrors.Is() và errors.As()Panic, defer và recoverÔn tập: trình phân tích tệp3Con trỏ và bộ nhớ
Cơ bản về con trỏ trong GoCon trỏ đến structTruyền theo giá trị và tham chiếuHàm new()Thu gom rác trong GoÔn tập: xây dựng danh sách liên kết6Đa hình trong Go
Đa hình qua interfaceĐịnh kiểu vịt trong GoQuy tắc thỏa mãn interfaceCác tập hợp đa hìnhTiêm phụ thuộcÔn tập: bộ xử lý thanh toán9Lập trình đồng thời và lập trình hướng đối tượng
Cơ bản về goroutineChannel và giao tiếpChannel có bộ đệm và không có bộ đệmCâu lệnh selectsync.Mutex và sync.RWMutexsync.WaitGroupThiết kế struct an toàn luồngÔn tập: worker poolTự luyện tập: Trình biên dịch Go online