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

  • Người tạo chủ đề Người tạo chủ đề sirhung1993
  • Ngày bắt đầu Ngày bắt đầu
Cho mình hỏi cái event queue bạn đề cập vs cái callback queue nó là 1 hay là 2 khái niệm khác nhau vậy?
Cái hình minh họa không được chính xác lắm, mình nhác vẽ lại nên trích nguồn từ ông khác.

Callback queue thì lại ko được chuẩn lắm, ví dụ như mình implement callback hell -> thì đúng ra callback nested ở trong cùng phải được thực thi trước -> rồi mới trả kết quả về callback ngoài -> tương tự cho đến hết callback.

Event-queue thì là nơi hứng kết trả trả về từ callback , khá giống BlockingQueue bên Java và channel bên Go-Lang.
 
Nodejs có worker threads ko có nghĩa nó multi-threading đâu, cái này là term của nó thôi. IIRC. Còn đó giờ cải tiến gì chưa thì ko biết, dự là ko, ko thể.

Tuy nhiên phần nội dung về hardware khá hay, tặng 1 vỗ tay.
Mình có rep tương tự ở trên , tùy vào scope vs định nghĩa về multi-threading.

Về mặt programming thì đúng là ko support tạo thread = tay , nhưng thực tế NodeJS có dùng nó để xử lý request -> nếu ko thì sẽ không có async / non-blocking IO
 
Sorry chủ thớt nhưng mình thấy một số ý của bạn trong phần Hardware có vẻ chưa thực sự chính xác. Mình sẽ giải thích lại theo cách hiểu của mình như sau:
  • CPU được tạo ra bằng cách kết nối các cổng logic. Các cổng logic này hoạt động dựa trên xung nhịp. Muốn tăng tốc độ của phần mềm thì cần giảm thời gian thực hiện 1 câu lệnh xuống bằng cách tăng xung nhịp lên. Tuy nhiên khi tăng xung nhịp thì sẽ dẫn đến tiêu hao điện năng và giảm tuổi thọ của CPU. Do đó người ta mới nghĩ ra kiến trúc pipeline để giải quyết chuyện này. Bằng cách chia nhỏ các bước trong một câu lệnh ra và các bước này sẽ có thể được làm overlap vào nhau. Thực tế nó có thể overlap không tùy tùy từng trường hợp cụ thể. Hardware nó sẽ tự xử lý để tối ưu (branch predictor). Ngoài ra thì compiler cũng giúp tăng được lượng overlap lên bằng cách reorder các câu lệnh để giải quyết một số trường hợp liên quan đến data dependency (Optimizing compiler). Điều này giúp tăng throughput tuy nhiên cũng làm tăng latency. Nó không thực sự liên quan đến khả năng concurency. Vì kể cả có chạy 1 thread duy nhất thì pipeline vẫn giúp tăng throughput. :big_smile:
  • Kiến trúc hiện đại nó có nhiều thứ hay ho hơn.
    • Đầu tiên phải kể đến là SIMD instruction. Cái này giống như thay vì cộng từng phần tử giữa 2 vector thì nó cộng nhiều phần tử trong 1 câu lệnh. 1 dạng song song nhưng tại instruction level. Anh em nào có xài numpy trong python và thắc mắc vì sao nó chạy nhanh thì 1 phần là do cái này. Ngoài ra còn do tối ưu về cache nữa.
    • Tiếp theo phải kể để là hyper threading. Chắc ae đều đã nghe qua mấy cái quảng cáo dạng như 2 nhân 4 luồng. Cái này dạng như 1 core nhưng nó có ít nhất là 2 cho mỗi thanh ghi đặc biệt (vd: program counter) để giữ được context của 2 thread khác nhau. Giả sử thread1 đang chờ load data từ main memory do bị miss cache,... Thì thread2 sẽ được thực thi. Do đó nó giúp tận dụng tối đa thời gian của CPU.
Idea 1 thì tùy vào định nghĩa của bác ntn là concurrency? Vì thứ tự instruction được fetch vào PC không được control hoàn toàn bởi application -> còn phụ thuộc vào OS-priority . Với mình thì việc tạo nhiều thread trên 1 process -> tăng tỉ lệ số instruction của 1 process được load vào PC -> chiếm được CPU cycle.

Có thể nhiều instruction của 1 process được load vào -> thì càng nhiều instruction đang được chạy tại 1 cycle nhất định -> số lượng instruction per cycle (IPC) càng tăng. Cách đo đạc hiệu năng còn phù thuộc vào chỉ số hit-rate của cache

Ý 2 thì mình không có ý kiến nhiều, ISA thì vẫn là x86 vs ARM là chính. Còn hiện đại hơn thì mình ko rõ. Cái SIMD kia , hình như trong Java là được dùng trong Atomic.
 
Tôi rất ủng hộ a thớt viết bài. Vừa đóng góp cho cộng đồng vừa để tranh luận.
Chỉ có điều tôi góp ý là Nodejs sử dụng worker threads chỉ đơn thuần là gọi các process bên ngoài từ only main thread thôi. Chẳng có gì là phức tạp và khó hiểu.

Bản chất worker threads là module của C/C++ nó không phải core của Nodejs nên làm sao gọi nó là thread của Nodejs được. Nguyên tắc thread là phải trong core của ngôn ngữ để còn đảm bảo synchronized về mặt toán học. Maths của C/C++ nó khác với JS (Nodejs).

Tóm lại: Nodejs + worker threads + muti cores = parallel
Nếu như lấy luận điểm này làm gốc, thì các LLVM-based như Rust,Haskell vs Go-lang cũng ko có core thread. Vì tụi nó phải dịch về LLVM -> từ LLVM thông qua lib kernals -> machine-code

Còn gọi các process ngoài thì mình nghĩ nó ko đơn giản, vì các process không share chung memory. Để share được state qua lại thì phải có context-swicth hoặc thông qua socket/memory-mapped IO khá lằng ngoằng.
 
Nếu như lấy luận điểm này làm gốc, thì các LLVM-based như Rust,Haskell vs Go-lang cũng ko có core thread. Vì tụi nó phải dịch về LLVM -> từ LLVM thông qua lib kernals -> machine-code

Còn gọi các process ngoài thì mình nghĩ nó ko đơn giản, vì các process không share chung memory. Để share được state qua lại thì phải có context-swicth hoặc thông qua socket/memory-mapped IO khá lằng ngoằng.
Đang nói về riêng Nodejs thớt nhé. Worker-thread không phải đẩy vào trong Nodejs core để thành muti-thread.
 
Mình thì hiểu đơn giản về việc một ngôn ngữ có support multi-thread hay không như thế này:

Khi mình viết 1 đoạn code
JavaScript:
statement1();
statement2();
thì có hay không khả năng một statement3 giờ ơi đất hỡi ở đâu đó (tất nhiên là trong cùng 1 chương trình, cùng ngôn ngữ đó) chạy chen vào không.
Với Javascript thì theo mình biết là không thể. Và vì thế lập trình viên cũng không phải lo lắng về concurrent modification trong Js.

Chứ các bác cứ lôi những cái sâu bên dưới như kernel thread, OS hay đến cả pipeline trong CPU ra thì vô cùng lắm. Cái mình muốn nhấn mạnh là chỉ giới hạn trong phạm vi ngôn ngữ đó thôi.
 
Tức là với pipe-line có 5 stages, thì đôi khi 1 số stage bị block bởi việc access-memory, thì 1 core vẫn chạy ở những stages khác.
Về lý thuyết nó có thể chạy 1 lúc 5 tasks.

Idea 1 thì tùy vào định nghĩa của bác ntn là concurrency? Vì thứ tự instruction được fetch vào PC không được control hoàn toàn bởi application -> còn phụ thuộc vào OS-priority . Với mình thì việc tạo nhiều thread trên 1 process -> tăng tỉ lệ số instruction của 1 process được load vào PC -> chiếm được CPU cycle.
Cái này cực kỳ sai nhé. Không có chuyện pipeline có 5 stage thì tại 1 thời điểm có thể chạy được 5 task khác nhau. Tại vì nếu chạy 5 task cùng 1 lúc trên 1 core thì hardware cần phải có đủ resource để giữ được context của 5 task cùng 1 lúc. Nên số lượng task chạy được cùng 1 thời điểm trên 1 core chả liên quan gì đến số stage trong pipeline cả. Chủ thớt hiểu sai về pipeline cmnr, :beat_brick: .

Hiện tại chỉ có hyper threading thì có thể chạy được 2 thread "đồng thời" trên 1 core thôi.
Tuy nhiên bản chất vẫn là tại một thời điểm thì chỉ có một thread được chạy thôi. Nhưng do hardware đã lưu context hiện tại của 2 thread nên khác với context switch tại OS, context switch tại hardware gần như là zero cost.
 
Sửa lần cuối:
Mình có rep tương tự ở trên , tùy vào scope vs định nghĩa về multi-threading.

Về mặt programming thì đúng là ko support tạo thread = tay , nhưng thực tế NodeJS có dùng nó để xử lý request -> nếu ko thì sẽ không có async / non-blocking IO
Theo tôi thì multi-threading ở đây là ngta nói về mặt thread programming. Thread phải create assign handle join các thứ đc, tức là programming đx ấy mới tính là multi threading lang. Nên node ko thể gọi là multi thread.
 
Cái này cực kỳ sai nhé. Không có chuyện pipeline có 5 stage thì tại 1 thời điểm có thể chạy được 5 task khác nhau. Tại vì nếu chạy 5 task cùng 1 lúc trên 1 core thì hardware cần phải có đủ resource để giữ được context của 5 task cùng 1 lúc. Nên số lượng task chạy được cùng 1 thời điểm trên 1 core chả liên quan gì đến số stage trong pipeline cả. Chủ thớt hiểu sai về pipeline cmnr, :beat_brick: .

Hiện tại chỉ có hyper threading thì có thể chạy được 2 thread "đồng thời" trên 1 core thôi.
Tuy nhiên bản chất vẫn là tại một thời điểm thì chỉ có một thread được chạy thôi. Nhưng do hardware đã lưu context hiện tại của 2 thread nên khác với context switch tại OS, context switch tại hardware gần như là zero cost.
like cho bác, " 1 core có 2 bộ phận chính là Program Counter(PC)Arithmetic Logic Unit (ALU) . cái này đã nói rõ tại 1 thời điểm chỉ có 1 thread được chạy, pipeline chỉ giúp tăng tốc độ xử lý khi có nhiều thread hoạt động tại cùng một thời điểm
 
Cái này cực kỳ sai nhé. Không có chuyện pipeline có 5 stage thì tại 1 thời điểm có thể chạy được 5 task khác nhau. Tại vì nếu chạy 5 task cùng 1 lúc trên 1 core thì hardware cần phải có đủ resource để giữ được context của 5 task cùng 1 lúc. Nên số lượng task chạy được cùng 1 thời điểm trên 1 core chả liên quan gì đến số stage trong pipeline cả. Chủ thớt hiểu sai về pipeline cmnr, :beat_brick: .

Hiện tại chỉ có hyper threading thì có thể chạy được 2 thread "đồng thời" trên 1 core thôi.
Tuy nhiên bản chất vẫn là tại một thời điểm thì chỉ có một thread được chạy thôi. Nhưng do hardware đã lưu context hiện tại của 2 thread nên khác với context switch tại OS, context switch tại hardware gần như là zero cost.
Bác đọc thêm slide của bác @Fire Of Heart thì sẽ refine được concept.

Đây đây, về pipeline thì mời các thím đọc slide này.
http://www.cit.ctu.edu.vn/~dtnghi/cod/ch6.pdf

Tôi định tóm tắt mà thấy cái slide này nó chi tiết và đầy đủ các ý cmnr :)
Bác nhìn slide trang 5-6 , thì thấy tại 1 thời điểm T xác định, có thể có tối đa 5 Instruction. Slide bác Fire Of Heart rất chi tiết, trước mình học ĐH = tiếng Anh , nên ko biết là pipe-line Việt hóa thành ống dẫn :D
Trước thì ông thầy lấy ví dụ lắp ráp xe của Ford cho phần này.
like cho bác, " 1 core có 2 bộ phận chính là Program Counter(PC)Arithmetic Logic Unit (ALU) . cái này đã nói rõ tại 1 thời điểm chỉ có 1 thread được chạy, pipeline chỉ giúp tăng tốc độ xử lý khi có nhiều thread hoạt động tại cùng một thời điểm
Có nhiều stages ko tốn CPU-cycle , nếu hardware support thêm Direct Memory Access (DMA), cho 1 vài instruction copy/swap thì tại stage như WB(writeback) CPU lại assign sang 1 bộ phận khác (MMU - memory management unit , GPU - graphic ...) .

Tức là 1 vài instruction, ko block việc fetch các câu instruction khác. Nên vẫn cùng 1 PC vs 1 ALU, nhiều instruction vẫn có thể pipe-line vào cùng 1 lúc. Tất nhiên, càng nhiều stages, kiến trúc hardware càng phức tạp.

Không phải instruction nào cũng tốn CPU-cycle, MIPS chỉ là 1 kiến trúc đơn giản để dạy học.

Tóm lại, mình đưa pipe-line vào trong topic này, vì nó là 1 yếu tố ảnh hưởng đến khả năng concurrency vs multi-threading . Giusp cho việc nắm được idea chính ở đây là : thread trên OS/application nó độc lập với core/thread trên hardware -> việc của dev là muốn chiếm nhiều CPU-cycle hơn thì phải tăng số thread -> tăng số instruction được fetch vào hardware tại 1 lúc nhiều nhất có thể. ( còn việc hardware optimize như thế nào là 1 câu chuyện sâu hơn nhiều)
 
Đang nói về riêng Nodejs thớt nhé. Worker-thread không phải đẩy vào trong Nodejs core để thành muti-thread.
Ý bác là về việc quản lý dependencies/modules của NodeJS ?

Có check qua document của NodeJs thì thấy có phần spawn sub-process:
https://nodejs.org/dist/latest-v17.x/docs/api/child_process.html

Cũng không hẳn là multi-threading, vì như mình nói ở trên, việc quản lý tương tác giữa các process khác phức tạp. Phức tạp hơn nhiều giua các Thread.
 
Hmm...Topic hay
Vấn đề này cần được xem xét thêm khía cạnh kĩ thuật lập trình, ngữ cảnh thực tế, và quan trọng nhất là kết quả cuối cùng.
- Lấy vd nền tảng API web service. Nếu bạn có 1 khối lượng lớn data, tầm 1trieu dòng chẳng hạn, và 1tr dòng này cần được call lên API để validate.
-Ý tưởng cơ bản là viết 1 tool, tạo thật nhiều thread call đến API nhiều nhất có thể, ít nhất là đến khi có thread trả về exception error. Kĩ thuật code cần phải kĩ, vì thực tế nó còn có các thao tác khác như update kết quả về DB nữa, số lượng connection tới DB cũng là 1 vấn đề.
- Loại 2 cũng là 1 tool chỉ có 1 thread, nhưng clone ra nhiều instance của tool này cho đến khi xuất hiện các exception error. Loại này ưu điểm là tạo nhiều instance cho phép dễ scale trên nhiều server, đặc biệt là khắc phục được vụ giới hạn IP của web service, và cũng dễ code nữa. Khi cần có thể turn off bớt instance khá đơn giản
Với bài toán này thì bottle-neck chính là ở việc call http -> nếu được thì mình sẽ xử lý batch-processing chứ ko call one-vs-one cho từng row để validate.

Việc tăng số thread khi call http thì performance ko tăng linear đến infinity -> tầm 4-8 thread thì sau đó performance ko tăng nữa ( do các yếu tố như băng thông của card mạng - NIC, số lượng request được buffer phía đầu nhận , http1/1 hand-shaking .... )

Chưa kể gửi nhanh và nhiều liên tục , thì nếu service đặt sau 1 firewall xịn -> detect DDoS và chặn ngay -_-
 
Mình thì hiểu đơn giản về việc một ngôn ngữ có support multi-thread hay không như thế này:

Khi mình viết 1 đoạn code
JavaScript:
statement1();
statement2();
thì có hay không khả năng một statement3 giờ ơi đất hỡi ở đâu đó (tất nhiên là trong cùng 1 chương trình, cùng ngôn ngữ đó) chạy chen vào không.
Với Javascript thì theo mình biết là không thể. Và vì thế lập trình viên cũng không phải lo lắng về concurrent modification trong Js.

Chứ các bác cứ lôi những cái sâu bên dưới như kernel thread, OS hay đến cả pipeline trong CPU ra thì vô cùng lắm. Cái mình muốn nhấn mạnh là chỉ giới hạn trong phạm vi ngôn ngữ đó thôi.
Như point 1 của bác thì NodeJS là multi-thread đó :D -> thử console.log() trong callback của setInterval() là thấy là chạy thứ tự ko xác định trước.

JS vẫn phải care về concurrent modification -> bác thử truyền 1 object từ global vào trong callback/promises ( cho 1 hàm random gì đó set value vào biến global). Thì lúc này gía trị của biến global là ko xác định được lúc ở runtime. Thử 1 cái array hoặc JSON từ global vào callback , rồi spam call thử là rõ.
 
Như point 1 của bác thì NodeJS là multi-thread đó :D -> thử console.log() trong callback của setInterval() là thấy là chạy thứ tự ko xác định trước.

JS vẫn phải care về concurrent modification -> bác thử truyền 1 object từ global vào trong callback/promises ( cho 1 hàm random gì đó set value vào biến global). Thì lúc này gía trị của biến global là ko xác định được lúc ở runtime. Thử 1 cái array hoặc JSON từ global vào callback , rồi spam call thử là rõ.

Bác có ví dụ không. Em đọc chẳng hiểu gì :LOL:
Em thử cái này thì thứ tự rất là ổn định :sweat:

JavaScript:
let i = 0;
setInterval(() => {
  console.log(i++);
}, 1000);
 
Bác có ví dụ không. Em đọc chẳng hiểu gì :LOL:
Em thử cái này thì thứ tự rất là ổn định :sweat:

JavaScript:
let i = 0;
setInterval(() => {
  console.log(i++);
}, 1000);
Update lại 1 tẹo thành array , thì sẽ show được cái race-condition
JavaScript:
// Update lại phần này, dùng array element
//let i = 0;
let arr = [0];

setInterval(() => {
  arr[0]=arr[0]++;
  console.log(arr[0]);
}, 0);

setInterval(() => {
  arr[0]=arr[0]+2
  console.log(arr[0]);
}, 0);

Dùng array thì nó sẽ pass lại cái ref ( mình ko chắc biến var/let trong JS có thread-safe hay ko, nên dùng array element ở đây)
bác thử xem nhé, nếu đúng nó mutate đúng thì ko bao gio có gía trị trùng lặp.
 
Sửa lần cuối:
mutate đúng thì ko bao gio có gía trị trùng lặp.
Bác expect cái "mutate đúng" là như thế nào.
Em thì hiểu đoạn code trên như sau:
Mã:
Gọi callback của setInterval thứ nhất là f1, callback của setInterval thứ hai là f2. Thứ tự thực hiện sẽ là
f1 vào queue
f2 vào queue
dequeue => f1 chạy => output 0, i = 1, f1 vào queue
dequeue => f2 chạy => output 3, i = 3, f2 vào queue
dequeue => f1 chạy => output 3, i = 4, f1 vào queue

dequeue => f2 chạy => output 6, i = 6, f2 vào queue
dequeue => f1 chạy => output 6, i = 7, f1 vào queue

dequeue => f2 chạy => output 9, i = 9, f2 vào queue
...

Không biết ý bác về cái vụ trùng lặp có phải là cặp 33, 66,... ở trên không? Nếu thế thì em thấy nó đúng những gì expect mà.
 
Idea 1 thì tùy vào định nghĩa của bác ntn là concurrency? Vì thứ tự instruction được fetch vào PC không được control hoàn toàn bởi application -> còn phụ thuộc vào OS-priority . Với mình thì việc tạo nhiều thread trên 1 process -> tăng tỉ lệ số instruction của 1 process được load vào PC -> chiếm được CPU cycle.

Có thể nhiều instruction của 1 process được load vào -> thì càng nhiều instruction đang được chạy tại 1 cycle nhất định -> số lượng instruction per cycle (IPC) càng tăng. Cách đo đạc hiệu năng còn phù thuộc vào chỉ số hit-rate của cache

Ý 2 thì mình không có ý kiến nhiều, ISA thì vẫn là x86 vs ARM là chính. Còn hiện đại hơn thì mình ko rõ. Cái SIMD kia , hình như trong Java là được dùng trong Atomic.

Bác đọc thêm slide của bác @Fire Of Heart thì sẽ refine được concept.


Bác nhìn slide trang 5-6 , thì thấy tại 1 thời điểm T xác định, có thể có tối đa 5 Instruction. Slide bác Fire Of Heart rất chi tiết, trước mình học ĐH = tiếng Anh , nên ko biết là pipe-line Việt hóa thành ống dẫn :D
Trước thì ông thầy lấy ví dụ lắp ráp xe của Ford cho phần này.

Có nhiều stages ko tốn CPU-cycle , nếu hardware support thêm Direct Memory Access (DMA), cho 1 vài instruction copy/swap thì tại stage như WB(writeback) CPU lại assign sang 1 bộ phận khác (MMU - memory management unit , GPU - graphic ...) .

Tức là 1 vài instruction, ko block việc fetch các câu instruction khác. Nên vẫn cùng 1 PC vs 1 ALU, nhiều instruction vẫn có thể pipe-line vào cùng 1 lúc. Tất nhiên, càng nhiều stages, kiến trúc hardware càng phức tạp.

Không phải instruction nào cũng tốn CPU-cycle, MIPS chỉ là 1 kiến trúc đơn giản để dạy học.

Tóm lại, mình đưa pipe-line vào trong topic này, vì nó là 1 yếu tố ảnh hưởng đến khả năng concurrency vs multi-threading . Giusp cho việc nắm được idea chính ở đây là : thread trên OS/application nó độc lập với core/thread trên hardware -> việc của dev là muốn chiếm nhiều CPU-cycle hơn thì phải tăng số thread -> tăng số instruction được fetch vào hardware tại 1 lúc nhiều nhất có thể. ( còn việc hardware optimize như thế nào là 1 câu chuyện sâu hơn nhiều)

Tăng số lượng thread khi quá số thread của CPU thì không làm tăng số instruction được fetch, nhưng có tác dụng tận dung thời gian rỗi của CPU nếu ứng dụng là I/O bound.
Vì cơ chế của OS là khi gặp I/O thì context-switch sang thread khác, và chỉ dựng dậy khi đã có kết quả.
Tăng quá nhiều thread thì có cái hại là overhead của OS (bao gồm context switch, scheduling, chiếm tài nguyên) có thể lớn hơn lợi ích thu được. Vậy nên người ta mới nghĩ ra coroutine.

Nhưng với ứng dụng CPU bound thì không có tác dụng mà chỉ toàn hại.

Ý 2 thì mình không có ý kiến nhiều, ISA thì vẫn là x86 vs ARM là chính. Còn hiện đại hơn thì mình ko rõ. Cái SIMD kia , hình như trong Java là được dùng trong Atomic.

Atomic được cài đặt bằng Fence, không liên quan đến SIMD.


Đây đây, về pipeline thì mời các thím đọc slide này.
http://www.cit.ctu.edu.vn/~dtnghi/cod/ch6.pdf

Tôi định tóm tắt mà thấy cái slide này nó chi tiết và đầy đủ các ý cmnr :)

Slide này cũng cũ rồi.
CPU ngày nay (khoảng 20 năm nay) ngoài pipeline thì họ sử dụng thêm một kỹ thuật khác gọi là Out-of-order execution (OOO). Tức là các instruction không chỉ bị chia thành nhiều pipeline stage khác nhau, mà việc thực thi các stage còn không theo thứ tự. CPU sẽ tự sắp xếp các các micro-ops (do 1 instruction chia nhỏ thành ở stage decode) rồi lập lịch để thực thi các stage tiếp theo, miễn là đảm bảo không bị conflict về dữ liệu. Thuật toán họ sử dụng để lập lịch là Tomasulo.

Chính vì OOO cho nên cùng một lúc CPU có thể thực thi được nhiều instruction (không conflict nhau). Nhưng nó không phải theo kiểu là số stage == số instruction tối đa. Mà là trong những stage cuối cùng của pipeline sẽ là bước chính thức thực thi micro-ops, việc này được thực hiện bởi các Execution Unit (EU). Số micro-ops tối đa mà CPU thực thi được trong một thời điểm == số ops được Retire (đã tính toán xong kết quả) ~ số EU.

VD đây là kiến trúc Zen 3. Có thể thấy 1 core CPU có đến hơn 14 EU.
Zen3_arch_5.jpg


Trong thực tế thì có nhiều lý do nên không thể nào sử dụng hết cùng lúc 14 EU đó được. Một trong số đó là do phụ thuộc về mặt dữ liệu giữa các instruction, một cách để cải thiện là simultaneous multithreading (SMT), phía Intel gọi là Hyper threading (HT), nhờ đó mà có thể cùng lúc có 2, 4, hoặc 8 luồng thực thi, vì các instruction thuộc các luồng khác nhau nên chắc chắn không phụ thuộc nhau về mặt dữ liệu (*), nên hiệu suất sử dụng các EU tăng cao hơn.

(*):dữ liệu đang nói đến ở đây là dữ liệu trên thanh ghi, còn trong memory thì CPU không có cách nào detect được dependency, coder phải dùng một cơ chế gọi là Fence để chỉ thị cho cpu biết thứ tự thực hiện các instruction nên như nào, tránh reorder lung tung.
 
Sửa lần cuối:
Bác nhìn slide trang 5-6 , thì thấy tại 1 thời điểm T xác định, có thể có tối đa 5 Instruction.
Vấn đề là 5 instruction này nó không nằm trong 5 thread khác nhau, cùng lắm thì chỉ là 2 nếu cpu là hyper threading. Vì nếu 5 instruction của 5 thread khác nhau cùng được load vào các stage khác nhau trong pipeline thì cần 5 program counter để có thể giữ được vị trí câu lệnh hiện tại của 5 thread. Nên kết luận số stage trong pipeline càng lớn thì càng lằm tăng khả năng concurrency là đang hiểu sai pipeline.
 
Update lại 1 tẹo thành array , thì sẽ show được cái race-condition
JavaScript:
// Update lại phần này, dùng array element
//let i = 0;
let arr = [0];

setInterval(() => {
  arr[0]=arr[0]++;
  console.log(arr[0]);
}, 0);

setInterval(() => {
  arr[0]=arr[0]+2
  console.log(arr[0]);
}, 0);

Dùng array thì nó sẽ pass lại cái ref ( mình ko chắc biến var/let trong JS có thread-safe hay ko, nên dùng array element ở đây)
bác thử xem nhé, nếu đúng nó mutate đúng thì ko bao gio có gía trị trùng lặp.

Bác expect cái "mutate đúng" là như thế nào.
Em thì hiểu đoạn code trên như sau:
Mã:
Gọi callback của setInterval thứ nhất là f1, callback của setInterval thứ hai là f2. Thứ tự thực hiện sẽ là
f1 vào queue
f2 vào queue
dequeue => f1 chạy => output 0, i = 1, f1 vào queue
dequeue => f2 chạy => output 3, i = 3, f2 vào queue
dequeue => f1 chạy => output 3, i = 4, f1 vào queue

dequeue => f2 chạy => output 6, i = 6, f2 vào queue
dequeue => f1 chạy => output 6, i = 7, f1 vào queue

dequeue => f2 chạy => output 9, i = 9, f2 vào queue
...

Không biết ý bác về cái vụ trùng lặp có phải là cặp 33, 66,... ở trên không? Nếu thế thì em thấy nó đúng những gì expect mà.
Mình mới update lại thành array-element, để đảm bảo nó access vào memory-address chứ ko phải 1 thread-safe object .

Thì kết qủa ra như thế này
Mã:
0
2
2
4
4
6
6
8
8
10
10
12
12
14
14
16
16
18
18
20
20
...

Tại sao lại có gía trị bị trùng lặp ở đây?
trong khi cả 2 callback để increment lần lượt là 1 vs 2.
Lý do là việc access memory như cách trên ở môi trường multi-thread sẽ gây ra race-condition ->
  • 1 thread fetch gía trị chưa được update ( dirty-cache)
  • increment trên cái gía trị cũ, rồi write-back ( đứa ghi sau, đè lên đứa trước )
 

Thống kê chủ đề

Ngày tạo
sirhung1993,
Người trả lời cuối
blanho,
Trả lời
142
Lượt xem
22.565
Quay lại
Lên đầu trang