thắc mắc Concurrency, Parallel, Asynchronous, Multithreading khác nhau như thế nào?

o0alvin0o

Senior Member
Em là dân trái ngành hiện đang làm Java Backend đuợc hơn nửa năm. Truớc khi học Java thì em có học C truớc (tự học), hiện tại đang có hứng thú với Rust chủ yếu viết những tool CLI linh tinh để phục vụ công việc chính. Cách đây không lâu thì có task liên quan tới async, thread, và cả OS. Khi tự tìm hiểu thì em thấy khó khăn vì không có foundation từ truờng lớp bài bản. Đây là một trò chơi hay nhưng cực kỳ risk nếu không hiểu rõ mình làm gì nên để hòan thành task em đã chọn phuơng án an toàn, đánh đổi performance và efficiency. Em nhờ các bác đi truớc giới thiệu cho em đầu sách để em tự nghiền ngẫm ạ. Em thích textbook hơn là xem nhưng nếu course chất luợng ở dạng video thì cũng không vấn đề gì.
Hiện tại em đang hiểu như sau:
  • Concurrency không nhất thiết phải dựa trên multithread vì chẳng hạn như call http request thì mình delegate task cho Socket IO, trong khi chờ response thì sẽ thực hiện những block code khác không depend vào result của socket task, tuơng tự với những device khác như Disk IO... và phần lớn việc implement đuợc compiler đảm nhiệm, dev chỉ cần declare what to do in asynchronous(C# chẳng hạn).
  • Parallel là những task thực sự đuợc execute cùng lúc và mỗi task có 1 thread riêng. Đến đây thì em lại có một thắc mắc khác là thread trong programming language mình sử dụng (với Java là JVM sẽ đảm nhiệm việc quản lý thread) có vẻ như không liên quan tới thread của OS, và thậm chí ở tầng hardware(cpu core, cpu thread) lại đuợc che đi sự phức tạp bằng 1 lớp abstraction nữa. Em cần lời khuyên là em nên tìm hiểu tới đâu, em sử dụng Java chủ yếu viết service phục vụ Enterprise là chính chả bao giờ đụng đến những cái này, Rust thì không biết tuơng lai sẽ như thế nào về nhu cầu việc làm.
  • Nếu 2 khái niệm trên em hiểu đúng, thì liệu synchronus có thể hiểu đơn giản là code run line by line, còn async thì code có thể run không theo thứ tự mình viết, lúc này lại dẫn đến một khái niệm khác cũng tuơng tự như thế là Reactive và Event driven-programming.
Đến đây thì em thực sự overload và confused nên rất cần sự giúp đỡ của các bác đi truớc. Em cảm ơn ạ
 
bạn hiểu sai rồi, int a = 1, b =2 , c = a +b, bạn chạy async đoạn này thử tôi coi
ồ, vậy async có nghĩa là execute những block code không phụ thuộc vào bất kỳ 1 tham số nào của current thread phải không bác. Mong bác giải thích giúp em.
 
async thì code có thể run không theo thứ tự mình viết
nhiều người hay hiểu async theo kiểu này nhỉ, mình thấy lúc nào cũng là chạy tuần tự thôi, chỉ là cái callback trong hàm async đó ko được invoke ngay trực tiếp, hay dân gian còn gọi là đẩy vào event loop để xử lý, sau khi xử lý ms đẩy vào callback queue để lấy ra thực thi
 
bác nói rõ hơn về task của bác dc giao ko :oops:

Gửi từ Realme RMX3371 bằng vozFApp
Architect ban đầy khi em nhận là có 1 service Window duy nhất viết bằng C#, service có nhiệm vụ monitor 1 folder, mỗi khi có file mới đuợc generate thì cần phải read content để detect thông tin xem có cần phải xử lý không. Trở ngại thứ nhất là nếu đọc ngay tức khắc thì có lúc content sẽ chưa đuợc gen ra đầy đủ, chữa cháy bằng cách truớc khi đọc thì để thread sleep vài giây rồi sau đó mới read. Đến đây thì lại gặp một trở ngại khác, đó là library mà em sử dụng để monitor folder có khả năng missing some events vì workload đôi lúc lớn bất thuờng, instance này có nhiệm vụ nhận event từ OS và run ở một thread khác với main thread của service, em đã filter chỉ nhận mỗi event file creation, và mỗi khi có event instance này sẽ bỏ vào buffer với default memory là 8KB(theo Mic docs). Em có 3 phuơng án để giải quyết, thứ nhất là sửa architect, nghĩa là tách ra thành 2 hoặc 3 services, 1 service chỉ đảm nhiệm việc monitor sau đó trigger service khác implement logic => Cách này cũng không hẳn là nhanh và tốn nhiều thời gian để sửa architect. Cách thứ hai là do việc implement logic đang nằm chung method với file event watching, em phải tách nó ra và bằng cách nào đó có thể tách riêng monitor thread với worker thread, lúc đó sẽ trách đuợc việc run out of buffer => cần phải hiểu về ngôn ngữ đủ sâu để implement cái này, và dĩ nhiên cũng không phù hợp với junior. Cách thứ ba là tăng buffer lên, đến đây thì khi em đọc docs, em có thể nâng buffer lên tối đa là 64KB nhưng not recommend và default là safe choice, và cũng tùy vào CPU của của server, em có thử tính thì chi tiết là: Mỗi một event sẽ cần 16 bytes chưa bao gồm file name, cũng không rõ ràng lắm nhưng em assume là file name ở đây là full file name (bao gồm cả filepath), mỗi ký tự UTF-8 là 2 bytes, v trung bình là 200 bytes cho 1 event, nếu để default thì cũng chỉ chứa đuợc 40 events 1 lúc, chưa kể việc thread sẽ sleep truớc khi read content và implement logic => quá nhiều biến số nên cũng không dám làm cách này nhưng cách này có thể coi là nhanh nhất.
 
Câu hỏi này hay này, e cũng đang học concurrency đây :smile: Để e chia sẻ suy nghĩ 1 chút.

1. Vd như bài này https://stackoverflow.com/a/1050257 nói là:
  • concurrency: 1 người có thể xử lý nhiều loại việc - vậy gọi là multi-tasking - và có thêm khái niệm context-switching
  • parallel: nhiều người xử lý công việc đồng thời - gọi là multi-threading - và có thêm khái niệm thread-safety
  • parallelism is a subset of concurrency

2. Còn asynchronous là xử lý bất đồng bộ = cách xử lý trong thread khác với thread hiện tại - vậy nên code đang thực thi ko bị block - và mình nhận kết quả công việc = callback/event. Và nếu cần block thread hiện tại cho đến khi cái async func trả kết quả thì js có await, Java có Future#get

3. Thread thì phải có abstraction layer nữa để VM dễ quản lý. VD Java thì map 1:1 với OS thread https://openjdk.org/jeps/425 "Today, every instance of java.lang.Thread in the JDK is a platform thread". À còn có khái niệm green thread rồi virtual thread nữa mà thôi e chưa tìm hiểu tới
nRlF7V2.gif


Muốn tìm hiểu thêm thì thím tìm specification của ngôn ngữ nào nó cũng có phần concurrency, hoặc google book có từ khóa là ra à, mà đọc cũng mệt phết
 
Câu hỏi này hay này, e cũng đang học concurrency đây :smile: Để e chia sẻ suy nghĩ 1 chút.

1. Vd như bài này https://stackoverflow.com/a/1050257 nói là:
  • concurrency: 1 người có thể xử lý nhiều loại việc - vậy gọi là multi-tasking - và có thêm khái niệm context-switching
  • parallel: nhiều người xử lý công việc đồng thời - gọi là multi-threading - và có thêm khái niệm thread-safety
  • parallelism is a subset of concurrency

2. Còn asynchronous là xử lý bất đồng bộ = cách xử lý trong thread khác với thread hiện tại - vậy nên code đang thực thi ko bị block - và mình nhận kết quả công việc = callback/event. Và nếu cần block thread hiện tại cho đến khi cái async func trả kết quả thì js có await, Java có Future#get

3. Thread thì phải có abstraction layer nữa để VM dễ quản lý. VD Java thì map 1:1 với OS thread https://openjdk.org/jeps/425 "Today, every instance of java.lang.Thread in the JDK is a platform thread". À còn có khái niệm green thread rồi virtual thread nữa mà thôi e chưa tìm hiểu tới
nRlF7V2.gif


Muốn tìm hiểu thêm thì thím tìm specification của ngôn ngữ nào nó cũng có phần concurrency, hoặc google book có từ khóa là ra à, mà đọc cũng mệt phết
Cám ơn bác, nếu vậy thì nếu muốn đào sâu vào vấn đề này nó sẽ phụ thuộc vào ngôn ngữ mình sử dụng nữa phải không bác, còn phần OS thread trở đi mình có thể bỏ qua để đơn giản hóa vấn đề nhỉ.
À mà nếu vậy thì parallel cần phải execute trên multicore hoặc multithread hardware, nếu thực sự như vậy thì lại càng rối rắm nhỉ.
 
Cám ơn bác, nếu vậy thì nếu muốn đào sâu vào vấn đề này nó sẽ phụ thuộc vào ngôn ngữ mình sử dụng nữa phải không bác, còn phần OS thread trở đi mình có thể bỏ qua để đơn giản hóa vấn đề nhỉ.
À mà nếu vậy thì parallel cần phải execute trên multicore hoặc multithread hardware, nếu thực sự như vậy thì lại càng rối rắm nhỉ.
Đúng rồi thím, cứ từ layer cao trước, hiểu ngôn ngữ đó quản lý memory như thế nào (memory model) rồi tìm hiểu sâu hơn. Concurrency programming khó lắm, nhiều người còn bị bug đầy (race condition, deadlock). Mà khổ nỗi nó lại rất cần trong production :beated:
 
thực tế là máy tính đa nhân như hiện nay nó chạy đồng thời nhiều phép tính trong 1 thời điểm, vấn đề là Os nó lập lịch ra sao để quyết định tác vụ là tuần tự hay đồng thời
 
Concurrency và parallel có bác nào bên trên trích dẫn rồi. Phần parallel theo mình để hiểu sâu thì code thử cái phần tạo thread thật và chạy 1 vài bài tương tự rồi đặt debug bằng các ngôn ngữ thấp. Theo mình là C là tốt nhất. Lúc đó thread đó trùng với thread hệ điều hành luôn. Đặt debug rồi vào nhìn stack/thanh ghi một lúc là có khái niệm. Hơn nữa thì trên IDE nó có cái dòng view assembly từng câu lệnh mình đang chạy, là hiểu thực tế chạy song song là như nào. Các máy tính của mình giờ CPU thực tế có nhiều hơn 1 core vật lý nên cách trên là ok để quan sát rồi.
Chỗ này sau khi nhìn thử nhiều thread chạy, sẽ xuất hiện câu hỏi tự nhiên là các thread này share thông tin với nhau thế nào, hay nếu nó cùng update một biến thì sao? Lúc đó sẽ xuất hiện các kỹ thuật lock, hoặc các kỹ thuật share-nothing, một kỹ thuật nữa là lockless. Nâng cao hơn nữa sẽ đến hoạt động của máy tính (CPU): cache layer, memory barrier, out of order, deadlock, live lock, ...
Tạm thế cho phần parallel.

Về phần async. Khi flow code đang chạy ví dụ:

op1
op2
op3

Mình chạy lần lượt thì là sync rồi, giờ ví dụ op2 là phần tốn time/nặng/thực hiện io. Về logic chương trình thì PHẢI chạy op2 rồi mới chạy op3. Thì bạn xử lý thế nào? Async là đẩy trong flow code của mình (thread hiện tại) chạy op1, sau đó đẩy op2 cho một thread/worker khác thực hiện (hiểu đơn giản là một lời gọi hàm f_op2() đi). Vì f_op2() thực hiện ở thread khác nên để op3 được thực hiện sau op2, bạn chỉ còn 2 sự lựa chọn: tại thread hiện tại thực hiện việc chờ f_op2() ở thread khác thực hiện xong rồi thực hiện op3. Code nó có dạng:

op1
f_op2()
wait_till_f_op2_done // thực ra lệnh get() ở future có thể impelemt đơn giản là luôn hỏi là f_op2() đã xong chưa. Thường có một cấu trúc/biến nào đó ghi trạng thái.
op3.

Hoặc một cách nữa hay hơn đó là khi thực hiện f_op2() thì truyền op3 vào luôn. Bên f_op2() ở thread khác sau khi done, nó sẽ chạy tiếp op3 --> logic code được đảm bảo.

Lúc đó code như sau:

op1
f_op2(callback to op3)

như thế thread hiện tại của chúng ta free, thì chúng ta trả lại quyền điều khiển cho thằng call flow code trên. Thường thì nó nằm trong 1 while(true) eventloop nào đó.

Tất nhiên kỹ thuật đang code tuần tự (sync) sang async thì cũng phải thay đổi cho phù hợp.

Chỗ async mình có lời khuyên: khi nào bạn biết dòng code của bạn chạy ở thread nào thì bạn sẽ hiểu nó.
 
Cám ơn bác, nếu vậy thì nếu muốn đào sâu vào vấn đề này nó sẽ phụ thuộc vào ngôn ngữ mình sử dụng nữa phải không bác, còn phần OS thread trở đi mình có thể bỏ qua để đơn giản hóa vấn đề nhỉ.
À mà nếu vậy thì parallel cần phải execute trên multicore hoặc multithread hardware, nếu thực sự như vậy thì lại càng rối rắm nhỉ.
multi core multithread hardware thì giờ framework ngôn ngữ nó làm cho mình hết rồi nên cung ko cần quan tâm làm gì
 
Tui ko rành Java, nên sẽ cố giải thích theo cách generic mà tôi hiểu nhất trong hơn chục năm làm coder (không nhất thiết phải theo sách vở):

- Concurrency (danh từ) tui thường dùng để chỉ số lượng task tối đa thực hiện đồng thời, số lượng thường bị giới hạn bởi số core CPU. Ví dụ máy bạn 4 CPU thì có thể chạy mượt 4 acc game VLTK cùng lúc. Từ acc thứ 5 thì sẽ xảy ra OS context-switching giữa tối thiểu 2 acc với nhau gây lag.

- (In) parallel: từ này tui hiếm khi dùng, chủ yếu để nói tại 1 thời điểm spawn nhiều thread/process song song “chia để trị” một cái big task nào đó. Và thường phải khai báo + xử lý một cái callback khi toàn bộ thread/process hoàn tất (hoặc callback ở mỗi thằng). Ví dụ ở Javascript là Promise.allSettled

- Asynchronous: dễ hiểu nhất là 1 tác vụ non-blocking: ví dụ khi đọc báo trình duyệt của bạn đang tải quảng cáo (tải ngầm) nhưng bạn vẫn cuộn chuột bình thường, khi tải xong nó mới popup cái ads banner cho bạn.

- Multithreading: Như mấy ông nói ở trên, khái niệm này cần nói rõ thêm là OS threading hay language/framework threading. Ví dụ như Python ko support multithread, nó tự quản lý đóng/mở thread tuỳ theo cách bạn gọi system library. Con máy ảo Erlang (BEAM) thì lại khác, bạn tạo 1 (green/virtual) thread trên BEAM thì nó phải qua bước preemptive scheduling rồi mới xuống tới OS thread, nên điểm lợi là bạn có code tạo ra cả triệu thread cũng ko làm tạch server, trong khi vài chục nghìn OS thread là đủ tạch.
 
Những thuật ngữ trên là cả cái môn hệ điều hành trên đại học, muốn giải thích là giải thích từ đâu?

Mỗi một hệ điều hành sẽ có định nghĩa về 1 process, thread khác nhau. Nhưng cơ bản thì windows, linux, macos được chuẩn hóa rồi. JVM thì không rõ nhưng có thể coi nó là một OS thôi

Khi một chương trình được nạp vào bộ nhớ để thực thi, thì chương trình đó sẽ trở thành tiến trình (process). Còn thread chính là bộ phận nhỏ nhất của tiến trình, có thể hiểu nó là một process trong process và được quản lý bởi hệ điều hành. Nói cách khác, cái mà hệ điều hành schedule là thread chứ không phải process như mấy giáo trình hệ điều hành hay nói. Việc phân bổ process / thread từ phần mềm sang phần cứng CPU cũng do hệ điều hành quản lý chứ không phải multiprogramming là ta được quyền quyết định thread nào chạy core nào của CPU
Concepts-_Program_vs._Process_vs._Thread.jpg

Mọi process đều có ít nhất 1 thread và nếu có nhiều hơn 1 thread thì ta gọi process đó đang chạy multithread

Synchronize, Asynchronize, Parallelism thực chất là tính chất khác nhau của multithread theo góc nhìn phần mềm. Parallelism là tính chất cho phép một process chạy nhiều công việc cùng lúc thông qua nhiều thread. Asyncronous là việc các thread chạy loạn xạ, không theo thứ tự nào cả. Việc các thread chạy loạn xạ thế này có thể sẽ gây ra lỗi race condition, do đó cần có các cơ chế synchronize bắt buộc các thread phải chạy theo thứ tự nào đấy hoặc một quy tắc chung nào đấy để dễ quản lý. Tuy nhiên, rõ ràng không phải lúc nào synchronize cũng tốt vì nó cản trở hiệu năng, cần phải xem trường hợp nào cần sync và async

Và việc quản lý các tính chất trên của thread, ta gọi chung là concurrency
 
Last edited:
Những thuật ngữ trên là cả cái môn hệ điều hành trên đại học, muốn giải thích là giải thích từ đâu?

Mỗi một hệ điều hành sẽ có định nghĩa về 1 process, thread khác nhau. Nhưng cơ bản thì windows, linux, macos được chuẩn hóa rồi. JVM thì không rõ nhưng có thể coi nó là một OS thôi

Khi một chương trình được nạp vào bộ nhớ để thực thi, thì chương trình đó sẽ trở thành tiến trình (process). Còn thread chính là bộ phận nhỏ nhất của tiến trình, có thể hiểu nó là một process trong process và được quản lý bởi hệ điều hành. Nói cách khác, cái mà hệ điều hành schedule là thread chứ không phải process như mấy giáo trình hệ điều hành hay nói. Việc phân bổ process / thread từ phần mềm sang phần cứng CPU cũng do hệ điều hành quản lý chứ không phải multiprogramming là ta được quyền quyết định thread nào chạy core nào của CPU
View attachment 1657685
Mọi process đều có ít nhất 1 thread và nếu có nhiều hơn 1 thread thì ta gọi process đó đang chạy multithread

Synchronize, Asynchronize, Parallelism thực chất là tính chất khác nhau của multithread theo góc nhìn phần mềm. Parallelism là tính chất cho phép một process chạy nhiều công việc cùng lúc thông qua nhiều thread. Asyncronous là việc các thread chạy loạn xạ, không theo thứ tự nào cả. Việc các thread chạy loạn xạ thế này có thể sẽ gây ra lỗi race condition, do đó cần có các cơ chế synchronize bắt buộc các thread phải chạy theo thứ tự nào đấy hoặc một quy tắc chung nào đấy để dễ quản lý. Tuy nhiên, rõ ràng không phải lúc nào synchronize cũng tốt vì nó cản trở hiệu năng, cần phải xem trường hợp nào cần sync và async

Và việc quản lý các tính chất trên của thread, ta gọi chung là concurrency
Đúng rồi bác, không thể giải thích trong một comment đuợc nên nếu đuợc bác có thể giới thiệu em một textbook để em tìm hiểu thì cảm ơn bác nhiều.
 
Architect ban đầy khi em nhận là có 1 service Window duy nhất viết bằng C#, service có nhiệm vụ monitor 1 folder, mỗi khi có file mới đuợc generate thì cần phải read content để detect thông tin xem có cần phải xử lý không. Trở ngại thứ nhất là nếu đọc ngay tức khắc thì có lúc content sẽ chưa đuợc gen ra đầy đủ, chữa cháy bằng cách truớc khi đọc thì để thread sleep vài giây rồi sau đó mới read. Đến đây thì lại gặp một trở ngại khác, đó là library mà em sử dụng để monitor folder có khả năng missing some events vì workload đôi lúc lớn bất thuờng, instance này có nhiệm vụ nhận event từ OS và run ở một thread khác với main thread của service, em đã filter chỉ nhận mỗi event file creation, và mỗi khi có event instance này sẽ bỏ vào buffer với default memory là 8KB(theo Mic docs). Em có 3 phuơng án để giải quyết, thứ nhất là sửa architect, nghĩa là tách ra thành 2 hoặc 3 services, 1 service chỉ đảm nhiệm việc monitor sau đó trigger service khác implement logic => Cách này cũng không hẳn là nhanh và tốn nhiều thời gian để sửa architect. Cách thứ hai là do việc implement logic đang nằm chung method với file event watching, em phải tách nó ra và bằng cách nào đó có thể tách riêng monitor thread với worker thread, lúc đó sẽ trách đuợc việc run out of buffer => cần phải hiểu về ngôn ngữ đủ sâu để implement cái này, và dĩ nhiên cũng không phù hợp với junior. Cách thứ ba là tăng buffer lên, đến đây thì khi em đọc docs, em có thể nâng buffer lên tối đa là 64KB nhưng not recommend và default là safe choice, và cũng tùy vào CPU của của server, em có thử tính thì chi tiết là: Mỗi một event sẽ cần 16 bytes chưa bao gồm file name, cũng không rõ ràng lắm nhưng em assume là file name ở đây là full file name (bao gồm cả filepath), mỗi ký tự UTF-8 là 2 bytes, v trung bình là 200 bytes cho 1 event, nếu để default thì cũng chỉ chứa đuợc 40 events 1 lúc, chưa kể việc thread sẽ sleep truớc khi read content và implement logic => quá nhiều biến số nên cũng không dám làm cách này nhưng cách này có thể coi là nhanh nhất.
1. Cái vụ đọc file vậy là trick rồi, thông thường thì resource đang được ghi (write) thì thằng khác không thể access để write được, nếu là mình thì mình sẽ thử open để write file được ko, nếu ko thì polling 1 khoảng thời gian sau đó thử open để write lại, nếu được thì mới read nó, cách này đỡ rủi ro hơn khi làm việc với big file đang được write và thời gian chờ nó chính xác hơn, với time số polling càng nhỏ thì càng chính xác.
2. Chưa hiểu đoạn sau lắm, vụ CPU buffer gì đó, nhưng nếu mình design application thì đây là perfect case để sử dụng message queue, mặc kệ workload như nào, cứ có event file change thì cho 1 message vào queue, muốn lấy ra lúc nào xử lý thì xử lý, không quan trọng nhanh chậm nhưng đảm bảo được là event sẽ được process.
 
Mình hiểu đại khái thế này với context Java:
Đầu tiên program sẽ chạy trên 1 thread (vd public static void main() method).

Asynchronous: mình tạo và push message lên Message Queue, để service khác consume. Thread hiện tại vẫn chạy bình thường và không bị block.

Synchronous: Khi mình tạo và post message qua REST endpoint (vd với RestTemplate hay WebClient), thì thread hiện tại sẽ ngừng lại và đợi kết quả từ REST endpoint call.

Multithreading: Nếu không muốn đợi REST endpoint call, thì mình sẽ viết thêm code để call REST endpoint với 1 thread khác (vd Thread/Runnable, Future/Callable, CompletableFuture, ExcecutorService). Lúc này có thể coi là multithreading và asynchronous. (Sau đấy có thể dùng Thread.join()/Future.get() để lấy kết quả và tiếp tục process.)

Concurrency: 1 CPU chỉ có thể xử lí được 1 thread ở 1 thời điểm. Nếu CPU chỉ có 1 core, thì lúc này không được coi là parallel mà chỉ coi là concurrency (nhiều task chạy và kết thúc trong các khoảng thời gian trùng lặp).

Parallel: Nếu CPU có 2 core trở lên thì lúc này code được coi là parallel vì mỗi core có thể xử lí 1 thread.
 
Back
Top