kiến thức [Event Tặng Title] Concurrency/multi-threading in the nut shell

Thread hay quá, cảm ơn chủ thread và các ace vozer đã đóng góp.
Mình đánh dấu để đọc thêm.
Thớt hay mà ít anh em chém gió đàm đạo quá. Tiếc quá ít thớt như thế nầy ..... Sau này có thêm thớt về phần driver & phần bảo mật thì khá ngon. Nhiều a em còn đúng quan tâm duy nhất tới fw + api = ???? Thậm chí còn ko bao giờ hỏi cái fw được viết bằng cái gì ?
 
đá lên phát
multithreading nó có khác so với parallel không v các bác ? như e hiểu parallel sẽ chạy đồng thời 1 lúc trên nhiều core của CPU, còn multithreading, ví dụ như trong java khởi tạo 1 thread sẽ mapping tới OS thread, lúc này CPU mới thực hiện chạy các OS thread này, và CPU có thể schedule chạy các OS này concurrent trong 1 core, hoặc chạy trên nhiều core
 
đá lên phát
multithreading nó có khác so với parallel không v các bác ? như e hiểu parallel sẽ chạy đồng thời 1 lúc trên nhiều core của CPU, còn multithreading, ví dụ như trong java khởi tạo 1 thread sẽ mapping tới OS thread, lúc này CPU mới thực hiện chạy các OS thread này, và CPU có thể schedule chạy các OS này concurrent trong 1 core, hoặc chạy trên nhiều core
multi-thread thì phải so với multi-process chứ so sánh gì lạ vậy fen.
 
Thường mình đi làm có đụng đến multi thread nhưng chỉ như kiểu là chia nhiều thread ra để làm các công việc xong xong và không đụng đến nhau.
Như kiểu các replicas của cùng 1 instance backend vậy.
Mình thấy để tận dụng được sức mạnh của multi thread thì phải áp dụng trong các bài toán mà có share resource.
Khi đó để MT có tác dụng thì phải đáp ứng 2 điều kiện.
  • Tính đúng đắn của dữ liệu khi có tương tranh giữa các thread.
  • Đảm bảo việc tăng số thread đồng nghĩa với việc tăng hiệu năng ở mức nào đó.
 
Theo ý kiến cá nhân, thì multi-threading là sub-set của concurrency , về mặt chạy trên hardware/OS thì 2 khái niệm kia gần như tương đương .

Về mặt ngôn ngữ lập trình thì theo doc của go-lang thì go-routine nó sẽ không hẳn là 1 thread. Tức là go-engine có cách nào đó để tổ chức và sắp xếp lại nhiều go-routine trên 1 thread ( của OS).
Việc khởi tạo thread cần tương tác với OS, nên đó là 1 operation rất là tốn kém và không hiệu quả.

Ngược lại như Java, thì khi gọi new Thread() -> thì thực tế JVM gọi OS tạo 1 thread cho tao .

Như go-engine thì có thể coi đó như 1 hình thái của Inverse of Control (IoC) -> dev ko cần tạo thread, tạo go-routine thôi, việc còn lại go-engine lo.
coroutine và thread (ó thread)là hai khái niệm khá là khác biết.
mặt khác thì go-routine cũng không follow theo lý thuyết chung về coroutine.
Trong go-routine thì go-engine sẽ xử lý việc context switch giữa các go-routine. Chỉ với một os thread thì go vẫn có thể chạy được rất nhiều go routine.
Ngoài ra có một điểm hay là các go-routine sẽ có thể chạy trên các thread khác nhau.
Trong go thì số lượng os thread mà go application có thể tạo ra sẽ bằng với số lượng cpu (default) -> điều này tăng tính concurency của go routine và làm giảm context switch của os thread. Em nghĩ đây là lý do mà goroutine được coi là có khả năng chạy đồng thời < capable of running concurrently>
 
3. Async/non-blocking IO được implement như thế nào ?

Ở trên có nhắc tới tại sao Async/non-blocking IO lại liên quan tới topic này.

Đơn giản là vì Async/non-blocking IO nói dễ hiểu là 1 design pattern dựa trên multi-threading/concurrency.

Mình chỉ async/non-blocking IO trên thread hiện tại -> phần sync/blocking được off-load tới 1 worker-thread ( hoặc 1 thread khác, ko phải thread hiện tại).

Lấy NodeJS built-in architecture làm ví dụ ( built-in nghĩa là support native của ngôn ngữ mà ko cần thêm 1 external lib, plugin hay extension nào cả)

1*iHhUyO4DliDwa6x_cO5E3A.gif
nguồn Medium của Rahul

Như hình trên thì event_loop kia có thể hiểu là nó được chạy trên worker-thread.
Main-thread
sẽ được trigger/interrupt chỉ khi nào callback-stack thực thi xong 1 callback nào đó.

Main-thread thì chỉ có 1 ( đây là lầm tưởng về NodeJS chỉ chạy được 1 thread) , nhưng worker-thread thì nhiều hơn 1 ( default là số core * 2 , số core thì hình như V8 lấy thừ file /proc/cpuinfo )

Luồng đơn giản:
  • Main-thread gặp callback -> vứt vào callback-stack -> tiếp tục fetch event từ event_queue ( kết quả được tính toán từ worker-thread) -> main-thread trả về kết quả
  • Worker-thread scan liên tục callback-stack -> xử lý callback -> đẩy kết quả vào event_queue
    • event_queue nhận được event mới -> trigger/interrupt main-thread ( phần này đọc doc thì dùng OS libuv )
    • trigger vs interrupt để tránh việc main-thread chỉ mải mê push callback vào stack mà ko đọc kết quả trả về
Sau khi em giải độc thì trong Nodejs k có khái niệm event queue bác ạ
 
à ko, ý e chỉ là cái khái niệm thôi, vì e thấy dễ bị nhầm lẫn là chạy đa luồng nghĩa là chạy song song đó
Theo em nghĩ parallelism nó chạy đồng thời process/thread trên các core của CPU (liên tục, không ngắt quãng). Còn concurrency nó chỉ tạo cảm giác các process/thread đang chạy song song bằng context switching (schedule process/thread cho CPU liên tục). Nếu số lượng thread của bác nhỏ nhỏ hơn hoặc bằng số core thì multi-threading/multi-process có thể nói là parallel, còn lại thì nó chỉ là concurrent.
 
Ngành computer architecture về cách hoạt động, xử lý ...từng ngóc ngách đều đã có rất nhiều tài liệu mô tả rõ ràng. Cái khó nhất là computer architecture exceptions, cực kỳ phức tạp và đánh đố tất cả các thiên tài, thần đồng về máy tính. Người ta ví việc giải một bài toán IPO sẽ là dễ hơn rất nhiều việc tìm & giải quyết một exception mới phát sinh (hệ thống log không ghi lại) của hệ thống phần cứng máy tính. Khi không tìm ra được thì phần lớn đều phải reset lại toàn bộ hệ thống, đôi khi là phải đập đi thay mới hoàn toàn. Thiệt hại lúc này là vô cùng lớn.

Thế nên mới có câu chuyện nhiều hệ thống máy tính cồng kềnh cũ kỹ nhưng chạy ổn định và nó vẫn đang hoạt động, ở các nước tiên tiến họ không phải là không muốn thay mới mà là thay mới rồi nếu phát sinh ra các exceptions giải quyết thế nào? Ai sẽ chịu trách nhiệm nếu để xảy ra những điều tồi tệ ấy?

Nhiều khi chúng ta cứ tranh cãi những thứ vốn đã được chính chủ giải thích cụ thể làm gì?
https://www.intel.com/content/www/u...pelining-1.html?wapkw=pipeline?wapkw=pipeline

Mình nghĩ, nếu topic này hướng đến computer architecture exceptions sẽ là rất hay đấy.

Thím nào muốn đi từ cơ bản tường tận về computer architecture thì đọc cuốn này
https://www.amazon.com/Modern-Computer-Architecture-Organization-architectures-ebook/dp/B083QJG28Y
anh có thể lập thread sharing 1 số kiến thức liên quan đến phần này dc ko. dạ cho em cảm ơn trước
 
Mình thấy trong go-lang có phần built-in xử lý multi-threading/concurrency khá hay là : go-routine vs channel nên đưa vào làm 1 case so sánh tương quan vs cách Java làm.

C-like:
package main

import (
    "fmt"
    "math/rand"
)

func CalculateValue(values chan int) {
    value := rand.Intn(10)
    fmt.Println("Calculated Random Value: {}", value)
    values <- value
}

func main() {
    fmt.Println("Go Channel Tutorial")

  values := make(chan int)
  defer close(values)

    go CalculateValue(values)

    value := <-values
    fmt.Println(value)
}
Nguồn https://tutorialedge.net

  • chan -> channel này đóng vai trò gần giống như BlockingQueue ở phần demo trên , thread-safe và là built-in type (tương quan primitive type bên Java) có sẵn và ko cần import lib nào cả.
    • Phần này mình chưa rõ cách go-lang implement chan này có liên quan gì trực tiếp đến OS ko, hay là 1 dạng cấu trúc dữ liệu thread-safe
    • Ai master go-lang xin thêm info để bổ sung.
  • go CalculateValue(values) -> trigger go-routine chạy function/lambda ở 1 thread-scope khác
    • có đọc sơ qua doc thì go-routine này ko hẳn là 1 thread-OS độc lập
    • thread-pool được go-lang tạo sẵn lúc start-up mà share chung cho nhiều go-routine
    • go-routine được cho là light-weight hơn cách Java new Thread() -> dễ hiểu là nó chỉ là 1 cấu trúc dữ liệu trên memory -> đẩy vào task-queue -> thread-pool có sẵn
    • Phần này mình suy đoán chỗ go-routine là in-memory datastructure , ai có cao kiến khác thì bổ sung ? ( như Java thread thì ko hẳn là in-memory , mà có 1 file_descriptor mô tả nó trên OS)
Lưu ý:
  1. gọi go-routine trong 1 scope function, thì khi thoát khỏi scope và trigger defer, thì go-routine sẽ bị clear -> life-cycle của go-routine phụ thuộc vào nơi gọi nó.
  2. Tương tự vs chan , cũng sẽ bị clear khi hết scope của function tạo ra nó
Cách làm của go-lang rất hay, trừu tượng hết phần tạo thread.
  • gọi go-routine trong 1 scope function, thì khi thoát khỏi scope và trigger defer, thì go-routine sẽ bị clear -> life-cycle của go-routine phụ thuộc vào nơi gọi nó.
    cái này hình như ko đúng nhé anh, goroutine ko bị clear miễn là main routine vẫn còn.
 
sẵn đây có cao nhân nào thông não cho em về co-routine của Kotlin với. Vì sao nó có thể chạy nhiều co-rountine trên main-thread mà ko block main-thread vậy nhỉ
nói 1 cách (rất) đơn giản thì khi bạn tạo những hàm suspensable thì nó sẽ có nhiều suspension point. Kotlin sẽ compile code của mình để hàm đó gặp sus point thì nó return hàm đó luôn, để chạy code khác.
Vậy làm sao code phía sau sus point dc resume? Trc khi return thì nó sẽ chạy phần async ở 1 thread khác, thread đó có giữ lại thông tin couroutine vừa rồi chạy tới đâu, khi nào xong thì thread đó resume lại coroutine đó ở đúng chỗ bọ return lúc nãy, và chạy tiếp.
Mình chỉ nói đơn giản cho bạn hình dung thôi. Chứ đương nhiên nó phức tạp hơn nhiều.
 
  • gọi go-routine trong 1 scope function, thì khi thoát khỏi scope và trigger defer, thì go-routine sẽ bị clear -> life-cycle của go-routine phụ thuộc vào nơi gọi nó.
    cái này hình như ko đúng nhé anh, goroutine ko bị clear miễn là main routine vẫn còn.
cái này sao bác biết vậy, hình như nó k được documented.
 
nói 1 cách (rất) đơn giản thì khi bạn tạo những hàm suspensable thì nó sẽ có nhiều suspension point. Kotlin sẽ compile code của mình để hàm đó gặp sus point thì nó return hàm đó luôn, để chạy code khác.
Vậy làm sao code phía sau sus point dc resume? Trc khi return thì nó sẽ chạy phần async ở 1 thread khác, thread đó có giữ lại thông tin couroutine vừa rồi chạy tới đâu, khi nào xong thì thread đó resume lại coroutine đó ở đúng chỗ bọ return lúc nãy, và chạy tiếp.
Mình chỉ nói đơn giản cho bạn hình dung thôi. Chứ đương nhiên nó phức tạp hơn nhiều.
Bro có vẻ hiểu sâu về cái này. Mình nhờ hỏi vài cái:
  • Đoạn suspend func trong Kotlin. Bản chất của nó có phải là function đặc biệt có thể tạm dừng và sẽ tiếp tục khi nào nó có thể. Vậy bằng cách nào nó có thể biết được một suspend function invocation có thể tiếp tục? Chắc phải có cơ chế notify nào đó
  • Suspend function chỉ có thể gọi được trong một suspend function. Vậy suspend function đầu tiên từ đâu mà ra?
  • Có một demo mà người ta launch 1000 koroutines, trong đó delay(100) và in ra 1 dòng log; và họ claim rằng nó nhanh hơn rất nhiều với việc sử dụng threads, thậm chí còn không thể. Vậy bằng cách nào nó có thể schedule mớ koroutines kia chỉ với 1 hoặc một lượng nhỏ thread?
  • Koroutine ko phải threads, vậy nếu spawn 1000 koroutines và gọi blocking call APIs (như đọc file chẳng hạn) thì có sinh ra 1000 threads ko?
 
Bro có vẻ hiểu sâu về cái này. Mình nhờ hỏi vài cái:
  • Đoạn suspend func trong Kotlin. Bản chất của nó có phải là function đặc biệt có thể tạm dừng và sẽ tiếp tục khi nào nó có thể. Vậy bằng cách nào nó có thể biết được một suspend function invocation có thể tiếp tục? Chắc phải có cơ chế notify nào đó
  • Suspend function chỉ có thể gọi được trong một suspend function. Vậy suspend function đầu tiên từ đâu mà ra?
  • Có một demo mà người ta launch 1000 koroutines, trong đó delay(100) và in ra 1 dòng log; và họ claim rằng nó nhanh hơn rất nhiều với việc sử dụng threads, thậm chí còn không thể. Vậy bằng cách nào nó có thể schedule mớ koroutines kia chỉ với 1 hoặc một lượng nhỏ thread?
  • Koroutine ko phải threads, vậy nếu spawn 1000 koroutines và gọi blocking call APIs (như đọc file chẳng hạn) thì có sinh ra 1000 threads ko?
Mình cũng hiểu sơ về nó thôi. Mình xin trả lời theo hiểu biết của mình, fence nào biết sâu hơn thì góp ý giúp.
- Suspend function thực chất nó k tạm dừng, nó thoát hẳn, sau đó sẽ có chỗ khác gọi lại chính hàm đó nhưng có tham số đặc biệt Continuation. Nhờ cái tham số đó mà hàm đó nó skip những gì đã chạy, và chạy đúng từ chỗ lần trc nó return. Cách kotlin implement coroutine gọi là CPS (Continuation Passing Style). Mình viết 1 hàm suspend thì compiler tự thêm 1 cái tham số continuation vào, dựa vào continuation đó thì sẽ biết dc bắt đầu chạy đoạn code nào.
Còn đương nhiên tác vụ cần chạy thì bắt buộc phải có thread nào đó để chạy, vì thread là execution unit của OS, chẳng qua nó chạy ở thread khác, k block cái thread invoke nó thôi.
Cái ví dụ delay của fence thì nó nhẹ hơn thread vì có thể hiểu nó có 1 cái timer, thread timer đó nó track tất cả coroutine, đến lúc thì nó resume lại couroutine đã suspend trc đó, nên nó k cần quá nhiều thread.
 
Mình cũng hiểu sơ về nó thôi. Mình xin trả lời theo hiểu biết của mình, fence nào biết sâu hơn thì góp ý giúp.
- Suspend function thực chất nó k tạm dừng, nó thoát hẳn, sau đó sẽ có chỗ khác gọi lại chính hàm đó nhưng có tham số đặc biệt Continuation. Nhờ cái tham số đó mà hàm đó nó skip những gì đã chạy, và chạy đúng từ chỗ lần trc nó return. Cách kotlin implement coroutine gọi là CPS (Continuation Passing Style). Mình viết 1 hàm suspend thì compiler tự thêm 1 cái tham số continuation vào, dựa vào continuation đó thì sẽ biết dc bắt đầu chạy đoạn code nào.
Còn đương nhiên tác vụ cần chạy thì bắt buộc phải có thread nào đó để chạy, vì thread là execution unit của OS, chẳng qua nó chạy ở thread khác, k block cái thread invoke nó thôi.
Cái ví dụ delay của fence thì nó nhẹ hơn thread vì có thể hiểu nó có 1 cái timer, thread timer đó nó track tất cả coroutine, đến lúc thì nó resume lại couroutine đã suspend trc đó, nên nó k cần quá nhiều thread.
Thank bro. Vẫn còn hơi khó hiểu. Chắc phải ngâm dần dần :D
Tìm sách này trên google ko biết xịn ko: Kotlin Coroutines: Deep Dive: Moskała, Marcin. Có time chắc ngâm cứu thử
 
Sau khi em giải độc thì trong Nodejs k có khái niệm event queue bác ạ
Chỗ sample code này Demo JS , bác để ý

parentPort.on('message', (mess) => {}

Có thể mình ko dùng đúng từ của Nodejs lưu cái event từ callback. Nhưng cơ chế sẽ gần như thế , sẽ có 1 loop check các callback trả về data chưa -> có thì xử lý tiếp.
 
Back
Top