CONCURRENCY TRONG GOLANG P1

KunkkaFollow
Last update: 2025-01-13,
6 mins to read

Concurrency và Parallelism

Có một sự thật là concurrency hay bị hiểu nhầm là parallelism. (Cả hai khái niệm đều dễ bị nhầm với một thứ chung chung như: một thứ chạy tại cùng một thời điểm với một thứ khác)

Thực ra: Concurrency là một thuộc tính của code, còn parallelism là một thuộc tính của chương trình đang chạy. Nếu tôi viết mã của mình với ý định cho hai phần của chương trình sẽ chạy song song, tôi có đảm bảo điều đó sẽ thực sự xảy ra khi chương trình được chạy không? Điều gì xảy ra khi tôi chạy code trên một máy tính chỉ có 1 core. Có thể một số người sẽ nghĩ nó được chạy parallel, nhưng không phải! Nó trông có vẻ như đang chạy song song, nhưng thực ra là đang chạy tuần tự với một cách nhanh hơn. CPU context chuyển đổi để share time giữa các chương trình, và trong một khoảng thời gian nhất định, các tác vụ có vẻ như đang chạy song song. Nếu chúng ta chạy cùng chương trình đó trên một máy có 2 cores, thì có thể chương trình sẽ thực sự chạy song song.

  • Điều này tiết lộ cho chúng ta biết, chúng ta thực ra không viết parallel code, chỉ có concurrent code và chúng ta hy vọng là chúng sẽ chạy song song. Tóm lại, parallelism là một thuộc tính ở runtime của chương trình, không phải thuộc tính của code.

  • Concurrent code của chúng ta có thực sự được chạy parallel không, điều này chỉ có thể được hiện thực hoá bới các lớp trừu tượng phía bên dưới như: concurrency nguyên thuỷ, program runtime, hệ điều hành, platform mà hệ điều hành chạy trên (containers, vm,..), và cả CPUs. Chúng cho phép chúng ta tách bạch concurrency và parallelism.

  • Parallelism là một hàm của thời gian, hoặc context. Context được định nghĩa như giới hạn của một operation được cho là atomic. Nó cũng quyết định giới hạn mà 2 hoặc nhiều operations sẽ được cho là parallel hay không.

Do not communicate by sharing memory. Instead, share memory by communicating.

concurrency-parallel

Go routines

Go routine là một trong những đơn vị cơ bản nhất của kiến trúc trong một go project, nên việc hiểu nó là gì, nó hoạt động thế nào là rất quan trọng. Mỗi chương trình go đều có ít nhất một go routines: main go routine - được tự động tạo và chạy khi chương trình bắt đầu.

  • Cơ bản go routine là một hàm chạy đồng thời (nên nhớ, không nhất thiết song song) với các code khác. Ta có thể dễ dàng định nghĩa nó bằng từ khoá "go"
func main(){
    go kissOzawa()
}
func kissOzawa() {
    fmt.Println("Moah Ozawa")
}
  • Go routines hoạt động thế nào? Go routines là unique với GO. Chúng không phải là OS threads và chúng không phải là green threads(thread được quản lý ở runtime của một ngôn ngữ). Chúng ở một tầng cao hơn của abstraction được gọi là coroutines. Coroutines đơn giản là những subroutines đồng thời (functions, closures, hoặc methods) mà không tiền nhiệm, không ưu tiên (khi nó chạy thì không thể bị dừng để nhường chỗ cho một tiến trình khác, ngay cả khi tiến trình khác có mức ưu tiên cao hơn. Nó chỉ rời khỏi cpu khi nó tự nguyện nhường. vd khi nó hoàn thành, cancel hoặc cần thực hiện IO). có nhiều điểm thông cho phép dừng hoặc nhảy lại vào trong một coroutine.

Go runtime sẽ giám sát hành vi của goroutines và tự động dừng chúng khi chúng block và tiếp tục chúng khi chúng trở lên unblock. Vì thế, có thể coi goroutines như là một class đặc biệt của coroutine. Coroutines và goroutines là các cấu trúc concurrent ngầm, nhưng concurrency không phải thuộc tính của một coroutine (wtf am I writing @@), thứ gì đó bắt buộc phải host một số coroutines cùng lúc và cho chúng cơ hội để thực thi, nếu không, chúng sẽ không concurrent!

Go sẽ host goroutines dưới M:N scheduler, nghĩa là map M green thread với N OS thread. Goroutines được schedule trên các green thread này. Khi chúng ta có nhiều goroutines hơn số green threads, scheduler sẽ phân tán các goroutines đến các threads có sẵn, và đảm bảo rằng khi một số goroutines bị block thì các go routines khác có thể chạy.

Goroutines thì rất rẻ và kể cả việc switching context cũng không hề đắt. Càng nhiều goroutines được tạo thì sẽ có càng nhiều processors được sử dụng nhưng việc tạo các go routines là khá rẻ, và bạn chỉ cần thảo luận về chi phí của chúng nếu bạn nghĩ chúng là nguyên nhân gốc rễ của các vấn đề về performance.

Còn tiếp...

(Bài viết có tham khảo từ Concurrency in Go và một số tài liệu, sách khác)


▶  Find out more: