Liệu bất đồng bộ chỉ hiệu quả với 2 công việc bất đồng bộ trở lên?

Chào các anh em muốn hỏi về bất đồng bộ trong NodeJS.
Giả sử em có các hàm như sau:

// Liệu ở đây bất đồng bộ có còn ý nghĩa không?
// Có thể khi file chưa đọc xong thì Response đã trả về
app.get('/', async (req, res) => {
    fs.readFile('/file.md', (err, data) => {
        if (err) throw err;
        console.log(data);
    });
    res.send('Hello World!');
});

// Ở đây mình lock lại bằng await thì không khác gì chạy đồng bộ
app.get('/', async (req, res) => {
    const data = await fs.readFilePromise('/file.md');
    res.send(data);
});

// Nếu cần đọc nhiều file thì em thấy mới có hiệu quả của async
app.get('/', async (req, res) => {
    const data = await Promise.all([fs.readFilePromise('/file.md'), fs.readFilePromise('/other.md')]);
    res.send(data);
});

Có ai có thể giúp em giải thích theo code em đã comment không ạ?

Đoạn code 1 bạn hiểu đúng rồi. Tuy nhiên code hơi vô nghĩa, khi mà đọc data từ file lại chỉ log ra console. Thay vào đó, nếu bạn cần trả data về cho response (kiểu res.send(data);) thì để có được data đó bạn phải dùng promise then hoặc await mới lấy được. Bạn hiểu chứ?

Đoạn code 2 await làm cho nó có vẻ đồng bộ (để code dễ hơn), nhưng nó vẫn là non-blocking. Cụ thể nếu bạn gọi request 2 lần thì hai request sẽ chạy riêng biệt nhau, không bị block lẫn nhau.

Đoạn 3 mình chưa hiểu lắm. Ý bạn đọc 1 file chưa hiệu quả là sao?

Ps: câu hỏi hồi nãy hay mà, sao lại xóa đi thế.

4 Likes

từ khóa “bất đồng bộ” là cái gì đó mà khiến rất nhiều newbie focus vào, trong khi nó chẳng có ý nghĩa gì nhiều với javascript cả, nó là một keywork chung chung chứ không riêng gì js

cái thật sự cần quan tâm là event driven hay event loop hay call stack, job queue các kiểu thì chả ai nhắc.

hậu quả là rất nhiều topic như thế này

11 Likes

Bạn mới code nên mình không đặt quá nặng câu chữ.
Nhưng chủ quan với mình mà nói thì thông qua 3 ví dụ, bạn đang chưa hiểu lắm về bất đồng bộ trong js (là chuyện quá bình thường với người mới với JS)

Sai nhất là cái đầu tiên. Nó k phải là “có thể” mà là chắc chắn. Async luôn chạy sau sync
2 cái sau thì thực tế không có cái gì được lock hết. Cú pháp chỉ giúp cho việc viết và quan sát code, cảm tưởng như sync, nhưng thực chất vẫn là async thôi.

Như mình nói, bạn còn mới nên chỉ cần nhớ comment đầu tiên của mình, là async luôn chạy sau sync. Còn 2 cái sau làm dần sẽ hiểu. Từ khóa thì @kisuluoibieng đưa hết rồi.

4 Likes

Em nghĩ là anh đang hiểu nhầm là cứ await là bị block server không chạy được nữa.
Nhưng em có ví dụ như này.

/**

 * Giả sử đây là 1 query lấy danh sách user tốn
 * nhiều thời gian nhưng liên quan tới I/O
 *
 */

function getUsers() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve({
                name: 'Phan Van Truong',
                id: 'id'
            });
        }, 10000);
    })
}

/**
 * Giả sử đây là 1 API với method GET để query dữ liệu users
 */

async function fakeGetUsersInfoRequest() {
    // Theo em hiểu là anh đang nhầm lẫn ở dòng await dưới sẽ
    // block cả server và server không thể tiếp nhận request nào khác
    const users = await getUsers();
    console.log('Lấy xong trả về kết quả: ' + users);
}

// User 1 gọi

fakeGetUsersInfoRequest();

// User 2 gọi

fakeGetUsersInfoRequest();

// Cả 2 đều chạy được gần như đồng thời chứ không hề bị block

/*

 Ví dụ:

    Ban đầu anh có 100% CPU 

     -> Khi user A gọi request đến fakeGetUsersInfoRequest thì anh còn 99% CPU.

        -> Chạy đến đoạn getUsers thì nó là non-blocking I/O nên sẽ đẩy sang bên thread pool để xử lý (CPU lại lên 100%)

          -> Lại có user B gọi request đến fakeGetUsersInfoRequest thì anh vẫn còn 100% CPU (không hề block)
             -> Sau khi có kết quả thì nó sẽ trở về message queue và trả response đúng request tương ứng
   
    Nó chỉ block server của anh khi hàm getUsers() có sử dụng tới tác vụ liên quan đến tính toán CPU nặng (vòng lặp gì đó,...)

*/

Hy vọng giúp được anh phần nào!

1 Like

Mình thấy câu hỏi lần trước thực sự rất hay, hi vọng bạn post trở lại.

3 Likes

Không biết bạn chủ thớt có thể tự khôi phục được topic hay không, mình lạm quyền paste nội dung của topic trước của bạn chủ thớt lên đây.

Chào các anh em mới học về NodeJS với Express, em có đoạn code sau gọi đến API GET
Và em có giả lập 1 hàm bất đồng bộ queryDB() trong 5s.
Nếu em mở 2 tab gọi đến server gần như đồng thời thì 1 request sẽ query mất tầm 5s. Và request thứ 2 sẽ bị block.
Vậy làm thế nào để NodeJS xử lý non-blocking được ạ?
Hay là em đã hiểu sai vấn đề rồi ạ :frowning:

const queryDB = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('MS');
        }, 5000);
    });
}

app.get('/', (req, res) => {
    await queryDB();
    res.send('Hello World!');
})
4 Likes

Với câu hỏi trên của bạn mình đã phải tự tay code thử lại chương trình, và kết quả test thử đúng như bạn nói. Request thứ 2 phải đợi request đầu chạy xong thì mới bắt đầu.

Tuy nhiên, đó không phải là do Node hoặc code có vấn đề, mà là bị browser giới hạn lại. Mình đã thử request lại với curl và mọi thứ bình thường, không bị blocking nữa.

Link chi tiết câu trả lời https://stackoverflow.com/a/50013445.

Ps: chỗ route có await phải viết là async (req, res) thì mới chạy được. Có thể bạn đó viết nhầm.

3 Likes

Code gốc sẽ tương đương với code này

function "queryDB" giữ nguyên

app.get('/', (req, res) => {
    queryDB().then(_ => console.log('Hello World!'))
   //console.log('This will always run, no delay');
})

nghĩa là nó queryDB xong thì mới log ra

==========================================
Quay lại cơ bản

  1. Code async/await sẽ đc viết lại thành như trên.
  2. QueryDB là async
  3. console.log là sync.
  4. Do không dùng cluster/child nên Node đang chạy trên 1 luồng duy nhất.
  • Khi lần 1, user A vào url, queryDB đc chạy, do nó là async -> xếp vào 1 chỗ, xử lý sau -> chạy xuống xử lý tiếp.
  • Như đã thấy trong code đc viết lại, không có lệnh nào ở dưới queryDB cả.
  • Cùng lúc đó, user B lại vào url, lại chạy queryDB -> xếp vào 1 chỗ, và xếp sau sau cái queryDB mà user A đã chạy ở trên
  • Hàng đợi hay queue sẽ thế này [queryDB (userA), queryDB (userB)]
  • Khi không còn code dạng sync nữa thì engine bắt đầu chạy code async ở trong queue. (thực ra event-loop nó phức tạp hơn nhưng tạm hiểu vậy đi - sync luôn chạy trước async)
    Nên user B phải đợi là dễ hiểu.

Để đạt được yêu cầu thì chỉ cần vứt cái console.log ra ngoài là đc (ví dụ như dòng comment ở code rewrite ở trên)
Hoặc đừng await nữa, vì await bản chất sẽ đc chuyển thành như code rewrite (tức là wrap toàn bộ code sau await vào trong hàm chính)

code đã xóa await (k dùng await nên bỏ async luôn)

app.get('/', (req, res) => {
    queryDB();
    res.send('Hello World!')
})

=====================================

Như vậy, nếu 1 task chạy quá lâu thì sẽ ảnh hưởng tới người dùng. Vì thế người ta mới nghĩ ra trò đa luồng, mỗi 1 user 1 luồng, không ai đợi ai hết.
Cluster/Child process trong node cũng có cơ chế tương tự như đa luồng.

=====================================
Không biết sao câu trả lời SO kia lại đc vote, rõ ràng không có chuyện browser chỉ giới hạn 1 request 1 lần, nghe đã vô lý, chưa cần test.

3 Likes

Thử thay đổi code trên thành.

app.get('/', (req, res) => {
    console.log("Start");
    queryDB().then(_ => console.log('Hello World!'))
})

Để mỗi khi có request đến thì dòng “Start” luôn được gọi ngay lập tức.

Nếu chạy 2 lệnh curl (ở 2 terminal khác nhau) thì hai dòng “Start” được in ra gần như cùng lúc. Trong khi đó nếu mở bằng browser thì lại bị delay khá lâu. Bạn giải thích sao về chuyện này?

4 Likes

Hay quá mình cũng muốn biết vì thấy cả 2 cách giải thích đều hợp lý

Cách bạn giải thích ở đoạn đầu khá là đúng tuy nhiên đến đoạn phản bác câu trả lời trong SO thì lại sai. Các trình duyệt khác nhau sẽ có những hành vi khác nhau khi thực hiện request. Bạn cần phải thực nghiệm thì mới có thể ra được câu trả lời thay vì tự cho là nó sẽ chạy

4 Likes

@tonghoangvu Mình vừa test không thấy hiện tượng này (Chrome lastest - Ubuntu 16)
Ngay từ đầu vào thì console start in ra luôn. Không có hiện tượng delay nào.

Cá nhân mình thấy curl hay browser cũng là 1, đều là tool dùng để gửi request về server cả

@qloved: Nếu bạn dựa vào chữ “thực nghiệm” để nói lại mình thì có lẽ không chính xác.

Cái mình muốn nói ở đây là cả việc giải thích event loop và curl mình đều chưa hề chạy thử, đều dùng kiến thức sẵn có để “phán”. Nên việc bạn nói mình phải “thực nghiệm” mới có quyền trả lời thì hơi vô lý (vì phía đoạn đầu của mình thì bạn lại nói là đúng - trong khi mình k “thực nghiệm”)

Mình hoàn toàn hiểu ý bạn là đừng có phán bừa khi chưa làm.
Tuy nhiên có những cái có thể ngờ ngợ là nó sai, kiểu như ví dụ sau: thời buổi khó khăn có ông nào tự dưng mời gọi góp vốn lời lãi lên tới 50%, thì có thể phán ngay tại thời điểm nghe là có gì không ổn với lời mời gọi này, cần tìm hiểu thêm.

Tương tự, với việc browser giới hạn litmit 1 request lại còn cùng 1 server nó vô lý vì mình làm việc với devtool hàng ngày, mỗi lần f5 nhìn thấy vài chục cái request chạy 1 lúc. Mình có quyền nói việc browser limit 1 request là sai.

Không phải cho tới bây h hay tới bài viết này mình mới biết browser limit bao nhiêu request, vì nếu mọi người đọc vài blog/sách về tối ưu hóa trang web thì sẽ thấy, giới hạn request cũng là 1 trong các bước phải làm.

Lý do mình không để source mà nói 1 cách thẳng băng vì 2 việc

  1. Mình test thử xem trong này có ai thực sự mở link SO, rồi test lại hoặc google câu dưới không
  2. Việc kiểm chứng lại câu nói của mình, hay bất kể ai trên mạng là chuyện của người đọc. Không phải cứ SO là ngon.
    Với 1 câu tìm kiếm đơn giản trên google: “browser limit concurrent request” thì nhiều kết quả lắm. Rất dễ kiểm chứng.
2 Likes
  • khi f5 có hàng chục requrest start cùng lúc nhưng bạn đã check timing của từng request chưa?
  • với target host khác nhau đôi khi sẽ có những giới hạn khác nhau.
  • với 1 người dev có kinh nghiệm nhưng dám phát biểu curl giống browser thì khá là thiển cận. Browser được implement nhiều tính năng khác nhau nhằm 1 mục đích nào đó. Và với mỗi phiên bản, loại browser, bản build khác nhau sẽ có những ứng xử khác nhau với từng môi trường. Ngược lại curl là tool dùng để gửi request và nó chỉ gửi request và bỏ qua mọi rào cản mà có thể browser sẽ chặn lại
3 Likes

Bạn hỏi câu lạ, không nhẽ mình f5 ngồi nhìn chơi cho vui?

Về curl vs browser, bạn nói nó đi đâu ấy, mình hỏi bạn là những cái bạn giải thích có đem áp dụng vào trường hợp này đc không? Không thì đừng lan man, ai cũng biết curl vs browser khác nhau ở ngay cái tên - một từ có 4 chữ, một từ có 6 chữ. Nhưng quan trọng là cái sự khác nhau về mặt ký tự này phục vụ gì cho vấn đề đang bàn tới mới là quan trọng.

Mình k ngại “phát biểu” bạn ạ, diễn đàn đâu có cấm? Và bởi vì diễn đàn không cấm nên người đọc mới cần chắt lọc thông tin bằng cách luôn tự kiểm chứng.
Việc bạn công kích cá nhân thay vì đưa ra những dẫn chứng cụ thể đủ hiểu bạn thích ăn thua thế nào rồi.

Mình xin phép không trả lời thêm bất kỳ comment nào từ phía bạn cả.

@tonghoangvu Mình rất mong tạo đc lại lỗi như bạn chỉ. Bạn thử thay con số 5000 = 5s, thành 30k hay 60k (30s - 1’) xem có tạo đc lỗi k? Mình thử với con số cực lớn thì thấy cả 2 đều in ra ‘start’ ngay lập tức. Lý do mình để to để tránh trừ đi thời gian switch giữa 2 tab/terminal.

const express = require('express')

const app = express()

const queryDB = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('MS');
        }, 5000);
    });
}

app.get('/', async (req, res) => {
    console.log('start');
    await queryDB().then(_ => {
        console.log(_);
    });
    res.send('Ok')
})

app.listen(3000, () => {
    console.log('Running');
})

Đây là toàn bộ code mình dùng để test.

Mình đang test với chrome trên Windows 10, có thể đây là nguyên nhân.

1 Like

mình mới check thì đúng bị lỗi thật, thế mà hôm trước k sao.

Hiện mình chưa có câu trả lời cho vụ này. Chắc phải đọc lại event-loop.
Hoặc cũng có thể là giới hạn browser, nhưng mình vẫn không thấy lý do này hợp lý.

4 Likes

Nếu thử hết tất cả các công cụ request URL đều bình thường mà chỉ mỗi trình duyệt có vấn đề thì vấn đề nó nằm ở bản thân trình duyệt thôi.
Như đã từng nói ở trên mà bạn có thể đã bỏ qua do thành kiến của bản thân nên bạn chưa đọc tới, trình duyệt sinh ra không chỉ mỗi việc request URL mà nó còn đảm nhận nhiều thứ khác phục vụ người dùng cuối lẫn developer. Việc 1 bản build có áp dụng giới hạn request hoặc có option enable giới hạn request đối với localhost thì tôi cũng chẳng có gì là lạ. (CORS là 1 ví dụ đơn giản cho thấy trình duyệt nó khác các tool command line khác như thế nào)

4 Likes

Mình nghĩ limit requests trên mỗi browser là có (Link SO: https://stackoverflow.com/questions/985431/max-parallel-http-connections-in-a-browser). Tuy nhiên @Lovemagic cũng đã cho thấy bạn ấy cầu thị bằng cách nhìn nhận có lỗi delay ở đây. Chúng ta không nên công kích nhau nữa, thay vào đó tìm root cause issue thì sẽ hay hơn

3 Likes
83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?