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

code .net toàn spam async await không não vào nhìn quả multithread bên java xong trầm cmn cảm
mMOqr4q.png
 
code .net toàn spam async await không não vào nhìn quả multithread bên java xong trầm cmn cảm
mMOqr4q.png
Phần async/non-blocking IO mình có giải thích ở trên. Là 1 pattern dựa trên trên multi-threading.

Java làm phần đó cũng gọn , ở trên mình demo là quản lý thread manual thôi. Dùng thư viện mutiny trong Java làm async/non-blocking cũng dễ thôi.
 
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.
 
Giờ vào phần của các web framework và cách nó dùng multi-threading.
Ở phần này mình lấy ví dụ của Quarkus ( 1 framework được back bởi RedHat)

Trước khi vào phần multi-thread, thì sẽ giới thiệu http trước:
  • Có 2 lib chính để xử lý http-request trong Java là Netty vs Vertx, vai trò của nó :
    • tạo http-socket
    • fefch chunk từ http-socket -> route tương ứng -> method tương ứng
    • trả về response
  • http-socket trong scope của OS là 1 vùng memory được map với Network Interface Card (NIC)
    • ví dụ tạo 1 port 6969 -> thực tế nó chỉ là 1 phân vùng memory tữ 0x696969... -> 0x9696969 (mang tính chất minh họa)
    • OS sẽ phân quyền process trên vùng nhớ này -> khi nhận được data-chunk từ NIC -> OS sẽ map vào phân vùng trên -> trigger hoặc được polling
Thì trong Quarkus có dùng 1 term là event-loop
proactor-pattern.png

Nguồn quarkus.io/guides
  • event_loop này là 1 thread-pool chịu trách nhiệm fetch data-chunk từ http-request
  • Trong trường hợp bt, thì event-loop này chỉ fetch data-chunk rồi push vào worker-thread pool
  • Nếu enable reactive(mutiny hoặc uni) hoặc dùng annotation @NonBlocking thì event-loop sẽ trực tiếp xử lý luôn logic với data-chunk đó mà ko cần switch qua worker-pool
  • Size của event-loop-pool được config tại start-up time: quarkus.http.event-loops-pool-size
  • Với nhiệm vụ chính là fetch http-request , nên nếu event-loops bị blocking quá lâu -> phần memory-mapped sẽ bị đầy queue -> web-service ko nhận được thêm data-chunk từ NIC -> treo
    • Đừng dùng event_loop cho những câu query vài giây hoặc vài phút
    • Đẩy task nặng qua worker-thread
Worker-pool thì tương tự:
  • Pool size được config trong application.yaml -> quarkus.http.worker-pool-size
  • worker-thread sẽ được nhận http-request từ event-loop ( tất nhiên là thông qua 1 queue ở giữa)
  • Tức là các end-point như thế này sẽ được gọi trong context của multi-thread
    • Java:
      @GET    @Path("/hello-wolrd")
          @Produces(MediaType.TEXT_PLAIN)
          public String hello() {
                  return "Hello";
          }
    • End-point là các method có logic để xử lý http-request -> và method này lưu ý là chạy trên context của multi-thread.
    • Nên lưu ý khi dùng những Singleton object vào scope của End-point -> nếu single ko thread-safe -> thì race-condition có khả năng xảy ra
Đa số web framework đều tận dụng rất tốt multi-threading/concurrency. Nên lưu ý context của end-point có đảm bảo thread-safe hay không tránh trường hợp race-condition. Ví dụ như truyền 1 biến global: boolean healthCheckOk vào scope của end-point, thì value của biến này là rất khó xác định nếu nó được nhiều thread cùng gọi.
 
Cách nhận biết 1 class có thread-safe hay không ? Khả năng bị dính race-condition:

Post này cần đọc qua phần Thread-scope trước

Vi dụ 1 class mà không đảm bảo thread-safe

Java:
@Singleton
public class TestClass {
        private boolean isOk = false;
        private final Map<String, String> str2str = new HashMap<>();

        public void setOk(boolean status) {
            isOk = status;
        }

        public boolean getStatus() {
            return isOk;
        }

        public void insertStr(String key, String value) {
            str2str.putIfAbsent(key, value);
        }
        
        public String getAKey (String key) {
            return str2str.get(key);
        }
    }

  • Ví dụ Testclass này được khởi tạo với bean scope là Singleton -> tức là cả application chỉ dùng duy nhất 1 object này cho tất cả những nới @Inject
  • khi gọi những method như setOk() vs insertStr()ở trong context multi-thread -> thì hiện tượng race-condition sẽ có khả năng xảy ra
    • Như bài thread-scope thì private boolean isOk = false này lúc được fetch vào CPU thì nó đã tồn tài trên L1,L2 hoặc L3 cache rồi
    • nếu 1 thread khác set biến này, vào 1 core có L1,L2 vs L3 cache khác thì giá trị lúc mình gọi getStatus() là ko xác định
  • Với cái HashMap thì còn tệ hơn, trong stack mới được fetch cái add của ref , phần key-value được lưu ở heap
    • nên khả năng sửa xóa các key-value sẽ override dẫn tới mất dữ liệu hoặc báo exception ConcurrenModification
Để nó thread safe thì
Java:
private volatile boolean isOk = false;

//hoặc

private AtomicBoolean isOk = new AtomicBoolean(false)
    
private final Map<String, String> str2str = new ConcurrentHashMap<>();

  • volatile force CPU phải check dirty cache trước khi dùng
  • Atomic thì dùng cả volatile vs compare and swap
  • ConcurrenHashMap có implement spinlock ( cũng dựa trên volatile)
 
Ngoài đề chút mong ông mod cho tâm tư vài lời nhé:
Thực ra hay đọc cái box CNTT này , cơ mà cũng oải. Rất lâu rồi có topic hay như nầy , chất lượng như nầy. Lúc đầu định viết vài dòng về bài toán coroutine vs event loop. Cơ mà đến bây giờ thì tự cảm thấy trình độ của kẻ ngoại đạo thật ko dám so bì với các thím ở đây. Cũng hơi buồn chút các "developer" Vn nói chung chắc ko thèm quan tâm tới mức này. Chẳng biết dùng từ gì cho "hạp" lý , chỉ mong các thím chung tay làm một bài toán cụ thể cho anh sáng tỏ - demo mấy thứ trên thì tốt quá. Ko biết các cụ có hưởng ứng ...
Phần routine trong Go tôi nghĩ các cụ cũng đi chi tiết + có demo chút. Món coroutine này bản chất nó có tới 2 kiểu chính " stackless coroutines stackful coroutines ". Các cụ đã nhắc , nhắc kỹ cho trót :LOL: cho bà con anh em chúng tôiđược hiểu rõ hơn.

  • 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)
( nhiều từ cố gắng dùng tiếng việt cơ mà tối nghĩa thực sự )
Cũng theo như mình được biết. Go-routine thuộc kiểu "stackful coroutines". Có Stack riêng cho từng thread của nó. Bản chất là "context switch- chuyển đổi ngữ cảnh" ở chế độ người dùng-user mode. Ko cần gọi tới api thread của OS ở chế độ " kernel mode ". Rất nhẹ tốn ít chu kỳ=> độ trễ sẽ thấp so với thread truyền thống của OS.
Tham khảo - https://blog.tsunanet.net/2010/11/how-long-does-it-take-to-make-context.html
Tuy nhiên thèn go này cũng có nhiều vấn đề ở cách tổ chức stack. Nói thì dài dòng các thím có thể đọc thêm ở vụ này.
https://agis.io/post/contiguous-stacks-golang/
http://www.mikemarcin.com/post/coroutine_a_million_stacks/
https://mail.mozilla.org/pipermail/rust-dev/2013-November/006314.html

Mong các thím chỉ giáo thêm về vấn đề này.
 
Last edited:
Ngoài đề chút mong ông mod cho tâm tư vài lời nhé:
Thực ra hay đọc cái box CNTT này , cơ mà cũng oải. Rất lâu rồi có topic hay như nầy , chất lượng như nầy. Lúc đầu định viết vài dòng về bài toán coroutine vs event loop. Cơ mà đến bây giờ thì tự cảm thấy trình độ của kẻ ngoại đạo thật ko dám so bì với các thím ở đây. Cũng hơi buồn chút các "developer" Vn nói chung chắc ko thèm quan tâm tới mức này. Chẳng biết dùng từ gì cho "hạp" lý , chỉ mong các thím chung tay làm một bài toán cụ thể cho anh sáng tỏ - demo mấy thứ trên thì tốt quá. Ko biết các cụ có hưởng ứng ...
Phần routine trong Go tôi nghĩ các cụ cũng đi chi tiết + có demo chút. Món coroutine này bản chất nó có tới 2 kiểu chính " stackless coroutines stackful coroutines ". Các cụ đã nhắc , nhắc kỹ cho trót :LOL: cho bà con anh em chúng tôiđược hiểu rõ hơn.


( nhiều từ cố gắng dùng tiếng việt cơ mà tối nghĩa thực sự )
Cũng theo như mình được biết. Go-routine thuộc kiểu "stackful coroutines". Có Stack riêng cho từng thread của nó. Bản chất là "context switch- chuyển đổi ngữ cảnh" ở chế độ người dùng-user mode. Ko cần gọi tới api thread của OS ở chế độ " kernel mode ". Rất nhẹ tốn ít chu kỳ=> độ trễ sẽ thấp so với thread truyền thống của OS.
Tham khảo - https://blog.tsunanet.net/2010/11/how-long-does-it-take-to-make-context.html
Tuy nhiên thèn go này cũng có nhiều vấn đề ở cách tổ chức stack. Nói thì dài dòng các thím có thể đọc thêm ở vụ này.
https://agis.io/post/contiguous-stacks-golang/
http://www.mikemarcin.com/post/coroutine_a_million_stacks/
https://mail.mozilla.org/pipermail/rust-dev/2013-November/006314.html

Mong các thím chỉ giáo thêm về vấn đề này.
Go-lang tôi có code qua thôi, đi support cho team khác nên không sâu.
Bác nào hard-core go-lang vào giải thích bản chất cái go-routine vs channel dựa trên cái gì ?
 
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.
Mình mới tìm được cách để chạy call-back ở worker-thread

JavaScript:
const { Worker, isMainThread, parentPort, threadId } = require('worker_threads');

const arr = [0];

if (isMainThread) {
  const worker = new Worker(__filename);
  setInterval(() => {
    arr[0] = arr[0] + 1;
    worker.postMessage(arr[0]);
    //console.log(worker.performance.eventLoopUtilization());
    console.log(`Worker.isMainThread: ${isMainThread} parent-scope: ${arr[0]} TID: ${threadId} `);
  }, 100).unref();
  return;
}


parentPort.on('message', (mess) => {
    arr[0] = arr[0] + 2;
    console.log(`Worker.isMainThread: ${isMainThread} worker-scope: and arr[0]: ${arr[0]} TID: ${threadId}`);
}).unref();
(function r(n) {
  if (--n < 0) return;
  const t = Date.now();
  while (Date.now() - t < 300);
  setImmediate(r, n);
})(10);

Kết qủa chạy ở latest LTS version :
node -v
v16.13.0

Code:
Worker.isMainThread: true parent-scope: 1 TID: 0
Worker.isMainThread: true parent-scope: 2 TID: 0
Worker.isMainThread: true parent-scope: 3 TID: 0
Worker.isMainThread: true parent-scope: 4 TID: 0
Worker.isMainThread: true parent-scope: 5 TID: 0
Worker.isMainThread: true parent-scope: 6 TID: 0
Worker.isMainThread: false worker-scope: and arr[0]: 2 TID: 1
Worker.isMainThread: true parent-scope: 7 TID: 0
Worker.isMainThread: true parent-scope: 8 TID: 0
Worker.isMainThread: true parent-scope: 9 TID: 0
Worker.isMainThread: false worker-scope: and arr[0]: 4 TID: 1
Worker.isMainThread: false worker-scope: and arr[0]: 6 TID: 1
Worker.isMainThread: false worker-scope: and arr[0]: 8 TID: 1
Worker.isMainThread: false worker-scope: and arr[0]: 10 TID: 1
Worker.isMainThread: false worker-scope: and arr[0]: 12 TID: 1
Worker.isMainThread: true parent-scope: 10 TID: 0
Worker.isMainThread: true parent-scope: 11 TID: 0
Worker.isMainThread: true parent-scope: 12 TID: 0
Worker.isMainThread: false worker-scope: and arr[0]: 14 TID: 1
Worker.isMainThread: false worker-scope: and arr[0]: 16 TID: 1
Worker.isMainThread: false worker-scope: and arr[0]: 18 TID: 1
Worker.isMainThread: true parent-scope: 13 TID: 0
Worker.isMainThread: true parent-scope: 14 TID: 0
Worker.isMainThread: true parent-scope: 15 TID: 0
Worker.isMainThread: false worker-scope: and arr[0]: 20 TID: 1
Worker.isMainThread: false worker-scope: and arr[0]: 22 TID: 1
Worker.isMainThread: false worker-scope: and arr[0]: 24 TID: 1
...

Như kết qủa trên thì mình khá chắc là thread trong NodeJs ko share cùng phân vùng memory.
Có thể thấy là ở parent-scope việc thay đổi gía trị ko ảnh hưởng gì đến worker-thread.
Cách duy nhất để sync là lúc worker xử lý xong và bắn message về parent -> 1 dạng queue

Có vẻ worker-thread được V8-engine bọc trong 1 process khác luôn. ( đây là đoán nhé, ko chắc chắn)
 
Mình mới tìm được cách để chạy call-back ở worker-thread


Như kết qủa trên thì mình khá chắc là thread trong NodeJs ko share cùng phân vùng memory.
Có thể thấy là ở parent-scope việc thay đổi gía trị ko ảnh hưởng gì đến worker-thread.
Cách duy nhất để sync là lúc worker xử lý xong và bắn message về parent -> 1 dạng queue

Có vẻ worker-thread được V8-engine bọc trong 1 process khác luôn. ( đây là đoán nhé, ko chắc chắn)
OK. Tóm lại khẳng định ban đầu của mình vẫn đúng (ít nhất là trong môi trường NodeJS hiện nay) khi mà các worker thread của NodeJS là các process riêng biệt của hệ điều hành. Và lập trình viên bình thường hoàn toàn không phải lo về concurrent modification (do không có share memory).

Tại sao mình nói ở trên là "ít nhất là trong môi trường NodeJS hiện nay"? Vì mình có đọc được là thực tế việc share memory này là có thể thông qua SharedArrayBuffer. Tuy nhiên có vẻ NodeJS chưa hỗ trợ. Có một implementation khác là Webworker (worker thread cho browser) hỗ trợ cái này rồi nhưng đang bị disabled bởi tất cả các major browsers (lý do???).
Một lần nữa, mình không phải là NodeJS programmer nên có thể thông tin của mình không theo sát được thực tế (bản thân NodeJS cũng phát triển nhanh quá), nếu fence nào có thông tin chính xác hơn xin vui lòng đính chính :beauty:
 
4. Vai trò của lambda function vs functional programming trong Async/non-blocking IO và multi-threading

Phần này sẽ đi sâu vào cách implement trong Java làm ví dụ:

Java:
final ExecutorService executorService = Executors.newSingleThreadExecutor();

executorService.execute(() -> {
            // Executed in another thread

           // Thread-scope
           // Dedicated stack-space (default 1MB on memory for each Thread)
});

// If the the task done -> then close (not closed yet)
executorService.shutdown();

// Block the current thread -> wait until the task done
if (executorService.awaitTermination(waitDuration, WAIT_TIME_UNIT)) {
            logger.info("onEnable init Ignite service successfully");
    // Success
} else {
    // Fail -> the task not finish on-time
  
    // Force the the thread to close
    executorService.shutdownNow();
}

Thread-scope là yếu tố tạo nên sự liên quan giữa lambda function/callback vs async/non-blocking IO.

  1. Các biến primitive datatype ( như int,long,boolean...) được lưu trên stack-memory của thread hiện tại, sẽ không cho phép share qua 1 thread(stack-memory) khác - trừ biến final
  2. Để truyền vào thread-scope khác, cần phải dùng wrapper-datatype ( Integer, Long, Boolean) hoặc Object được shared qua Heap-space. Lưu ý, về lý thuyết thì share được , nhưng sẽ không đảm bảo thread-safe, và compiler cũng bắt buộc phải là biến final ( trên stack lưu address -> heap-memory, thì phải final cái address này rồi mới truyền vào thread-scope khác, không cho phép override lại )
  3. Khi truyền biến vào 1 thread-scope khác, cần đảm bảo sẽ không có race-condition (thread-safe object) hoặc chỉ có 1 thread duy nhất access vào nó.
Giải thích sơ qua lý do tại sao có các ràng buộc trên:
  • Về mặt hardware, khi CPU xử lý machine-code, thì nó fetch data từ register -> L1,L2,L3 ... cache -> memory
  • Và mỗi core lại có 1 phân vùng L1 vs L2 riêng, có thể L3 lại chung (đọc thêm về MESI protocol) -> giá trị của 1 memory-address có thể bị dirty-cache (khác nhau giữa core vs core kia)
    • với point 1 và 2 ở trên thì nếu compiler cho phép truyền trực tiếp memory-address từ thread này sang thread khác -> việc update memory có thể dẫn đến race-condition hoặc unknow-condition
  • Để bắt buộc CPU check dirty-cache trước khi fetch giá trị -> dùng keyword volatile (tương tự như keyword register của C++) cho attribute muốn check -> ảnh hưởng đến performance rất lớn
  • Việc đảm bảo chỉ 1 thread duy nhất được tác động vào 1 Object -> forEach() , một vài stream api
    • hoặc có thể dùng keyword synchonized cho method -> đảm bảo chỉ có 1 thread duy tại 1 thời điểm có thể gọi vào method đó
Lambda function hay functional programming đều rất hạn chế mutate global var , best practice thì chỉ dùng pure-function ( không sửa xóa các vùng share).
Cá nhân mình thì hay dùng các thread-safe object ví dụ: BlockingQueue, ConccurentMap vs Atomic để sync giữa các thread.

Thread-safe object thường dùng cơ chế như spin-lock, semaphore hoặc đặc biệt như class Atomic thì support ở tầng assembly ( có các lệnh để tự sync value - update là dùng volatile + Compare and Swap - CAS )
truớc e dùng thì thấy executorService.shutdownNow() nó cũng không thể interupted thread được. Lý do thread này là ở trong pool, kb bác thấy có đúng k ?
 
truớc e dùng thì thấy executorService.shutdownNow() nó cũng không thể interupted thread được. Lý do thread này là ở trong pool, kb bác thấy có đúng k ?
Lúc gọi shutdownNow() thì pool sẽ lần lượt close hết thread trong pool.

shutdown() thì chỉ ko nhận thêm task, chứ những task đang chạy sẽ vẫn chạy cho đến khi awaitTerminate() timeout.

Sau khi gọi shutdownNow() thì gần như thread sẽ bị đóng dần ngay sau đó, mainthread ko bị block bởi operation này.
 
Thớt này quá là hard với 1 thằng dev quèn như tôi :(
Các ông cứ tự bi , tôi dân ngoại đạo cũng vọc có làm sao. Thớt đang hay lại dừng rồi , cơ mà toàn các khái niệm cũng quen. Giờ mạnh dạn đề nghị anh em làm cái lib bằng asm vs C or C++ để mô phỏng lại go-routine. Đảm bảo hiểu vấn đề ngay mà ... TÔi đang chờ các anh vụ này.

@sirhung1993 cụ cũng nói gần sạch hết các vấn đề rồi , cụ rảnh ko làm cái trên đê cho vui cửa vui nhà. Có khá nhiều vấn đề hay ho theo tôi biết cũng chưa hẳn có phương án ngon. Cơ mà hình như anh em dev ít quan tâm tới "low entry" nhở ?
 
Last edited:
Các ông cứ tự bi , tôi dân ngoại đạo cũng vọc có làm sao. Thớt đang hay lại dừng rồi , cơ mà toàn các khái niệm cũng quen. Giờ mạnh dạn đề nghị anh em làm cái lib bằng asm vs C or C++ để mô phỏng lại go-routine. Đảm bảo hiểu vấn đề ngay mà ... TÔi đang chờ các anh vụ này.

@sirhung1993 cụ cũng nói gần sạch hết các vấn đề rồi , cụ rảnh ko làm cái trên đê cho vui cửa vui nhà. Có khá nhiều vấn đề hay ho theo tôi biết cũng chưa hẳn có phương án ngon. Cơ mà hình như anh em dev ít quan tâm tới "low entry" nhở ?
Chắc nó viết đâu đó trong Go-lang github mà tôi lục mãi mà ko thấy,
khả năng viết ở phần sâu hơn như phần engine/compiler ...

Giờ đa số compile về LLVM rồi mới về machine-code. Viết trực tiếp ASM thì ít lắm.

Còn về C/C++ example thì tôi nghĩ ví dụ của Java trên tôi viết là phần nào đó cover rồi :D
 
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ỉ
Bản chất của coroutine vẫn là callback. Nó cũng chỉ là đẩy work sang 1 thread khác ngoài main thread thông qua khối withContext xong dùng callback gọi lại . Cái coroutine nó đơn giản việc bác viết callback. Thay vì callback lồng nhau oằn tà là vằn thì với coroutine có thể viết dạng tuần tự.
 
Chắc nó viết đâu đó trong Go-lang github mà tôi lục mãi mà ko thấy,
khả năng viết ở phần sâu hơn như phần engine/compiler ...

Giờ đa số compile về LLVM rồi mới về machine-code. Viết trực tiếp ASM thì ít lắm.

Còn về C/C++ example thì tôi nghĩ ví dụ của Java trên tôi viết là phần nào đó cover rồi :D
đúng rồi cụ giờ đa số compile trước LLVM rồi mới về machine-code. Cơ mà đã nói đến routine thì đa số phải can thiệp vào compiler để có cách dịch riêng cụ à.
Tuy nhiên vs c or c++ vẫn có thể viết vài đoạn asm để làm việc này. Dù nó có vài cái hạn chế khó kiểm soát - nhất về phần stack. Ko ai có thể dự đoán chính xác size của stack frame cả. Cơ mà tôi đang định làm một đoạn asm để test cái coroutine stackless. Thực sự vọc cái này cũng thấy hay , chỉ có điều chưa hiểu nó có lợi hơn việc dùng event-loop ở đoạn nào ? Chắc chỉ có cách dùng code một cách tường minh hơn về mặt ngôn ngữ.- suy nghĩ cá nhân. xin nhận mọi góp ý gạch đá.

Bản chất của coroutine vẫn là callback. Nó cũng chỉ là đẩy work sang 1 thread khác ngoài main thread thông qua khối withContext xong dùng callback gọi lại . Cái coroutine nó đơn giản việc bác viết callback. Thay vì callback lồng nhau oằn tà là vằn thì với coroutine có thể viết dạng tuần tự.
Đoạn này theo mình nghĩ cũng có liên quan tới callback cơ mà chính xác hơn. Coroutine nó chính là context switch. Lập lịch hợp tác....
Khi một hàm chạy , nó sẽ ko dừng lại cho đến khi tới return or kết thúc hàm. ( về mặt ngữ nghĩa - chứ cpu thì chưa chắc ). Coroutine khác một chút , một hàm có thể dừng lại một vị trí nhất định. Tí nữa có thể back về hàm đó chạy tiếp tại vị trí đã dừng. Như vậy vấn đề đặt ra cái stack frame phải được save lại từ vị trí dừng. Và restore-khôi phục khi tiếp tục chạy. Vs một số ngôn ngữ thiết kế theo mô hình stack- chẳng biết nói như nào. Cái này dẫn đến vụ stack loạn xì ngầu ...thường phải thiết kế lại compiler. Các thím có thể tìm hiểu thêm nhiều lib lắm.
 
Last edited:
Back
Top