Programming¶
- Processes and threads
- Error handling
- Async programming
- Memory management
Processes and threads¶
Concepts¶
-
program
: tồn tại trong bộ nhớ hoặc ở trạng thái nghỉ,program
có thể là một tập lệnh dựa trên văn bản (tệp DOS, tập lệnh Windows Powershell, tập lệnh bash shell, Javascript, v.v.) hoặc tập lệnh thực thi dựa trên nhị phân (.exe). -
process
: Là mộtprogram
được nạp vào bộ nhớ để bộ xử lý, thực thi. (loaded into the memory to be executed by the processor.) -
thread
: Là đơn vị thực thi tối thiểu của các đoạn mã do hệ điều hành quản lý. Trong hệ điều hành, mộtprocess
có thể được chia thành cácthread
.
Process vs. Thread¶
Comparison Basis | Process | Thread |
---|---|---|
Definition | Là một program đang được thực thi |
Là một process được chia nhỏ, có thể được quản lý độc lập bởi schedular |
Context switching time | Đòi hỏi nhiều thời gian hơn để chuyển đổi context vì chúng nặng hơn | Yêu cầu ít thời gian hơn để chuyển đổi context vì chúng nhẹ hơn |
Memory Sharing | Hoàn toàn độc lập và không chia sẻ bộ nhớ | Có thể chia sẻ một số bộ nhớ với các thread ngang hàng của nó. |
Communication | Đòi hỏi nhiều thời gian hơn | Yêu cầu ít thời gian hơn |
Blocked | Trong một nhóm process ngang cấp, nếu một process bị block, các process còn lại có thể tiếp tục thực thi. |
Nếu một thread bị block, tất cả các thread ngang hàng của nó cũng bị block. |
Resource Consumption | Yêu cầu nhiều tài nguyên hơn | Cần ít tài nguyên hơn |
Dependency | Các process độc lập với nhau |
thread là các phần của một process và do đó chúng phụ thuộc vào process |
Data and Code sharing | Có data và code segment độc lập | Một thread chia sẻ data segment, code segment, file, v.v... với các thread ngang hàng |
Treatment by OS | Tất cả các process khác nhau được hệ điều hành xử lý riêng biệt. |
Tất cả các thread ngang hàng nhau được hệ điều hành coi như một tác vụ duy nhất. |
Time for creation | Tạo tốn nhiều thời gian hơn | Tạo tốn ít thời gian hơn |
Time for termination | Cần nhiều thời gian hơn để kết thúc | Cần ít thời gian hơn để kết thúc |
Error handling¶
Concepts¶
-
Khi một
process
thực thi bị thất bại thìprogram
sẽ bịerror
. Và quá trình đi từ dự đoán, phát hiện và giải quyết cácerror
này, giúp cho cácprocess
thực thi thành công được gọi làError handling
. -
Quá trình
Error handling
bao gồm phần cứng và phần mềm. Việc xử lý lỗi thường được phát hiện bằng việc trả về cácError code
hoặc cácError message
cụ thể. -
Trong quá trình Runtime của
program
, có thể xảy ra tình trạng lỗi qua cácexception events
, dẫn tớierror
: dữ liệu không hợp lệ, không tìm thấy file, tràn bộ nhớ v.v. Và để xử lý tình trạng này, người ta dùng kỹ thuậtException Handling
- một kỹ thuật xử lý cácRuntime Error
,Exception Events
. Kỹ thuật này giúp duy trì cácprocess
chạy theo đúng nghĩa của nó, nếu có lỗi thì sẽcatch
cácError code
hoặc cácError message
cụ thể.
Error handling in Nodejs¶
- Một
exception
được tạo bằng cách sử dụng từ khóathrow
: - Sau khi JavaScript thực thi dòng này, chương trình tạm dừng và lỗi được gửi tới
exception handler
- một câu lệnhtry/catch
:
-
Trong Node.js, không throw
hoặcstrings
, mà là throwError objects
: -
Error handling
trong NodeJS vớiasync/await
:
Async programming¶
Concepts¶
-
Javascript Engine
:V8 Javascript Engine
- optimized version, bao gồm hai thành phần chính:Call Stack
: stack chứa các lời gọi hàm khi code được thực thi.Memory Heap
: Cấp phát bộ nhớ cho cácprocess
-
Event Loop
vàCallback Queue
: Bên cạnhJavascript Engine
, browser còn cung cấp cácNode APIs
, mộtEvent Loop
và mộtCallback Queue
. Chúng chạy trên cácthread
riêng và được browser kiểm soát đồng thời (concurrency
).- Các hàm
async callback
sẽ được thêm vàoCallback Queue
. - Nhiệm vụ của
Event Loop
là đợi đến khiCall Stack
rỗng, và quay lại kiểm tra trongCallback Queue
có gì không, nếu có thì lấy lần lượt chúng đẩy vào trongCall Stack
để chạy tiếp.
- Các hàm
Synchronous vs. Asynchronous Programming¶
-
Asynchronous programming
: Về bản chất, nó thực hiện các request đồng thời, ngay cả khi chúng ở các chức năng khác nhau. Một single thread sẽ được handle multiple-requests trongevent-loop
. Vì vậy, việc một request bị reject sẽ không ảnh hưởng đến request khác.NodeJS cho phép thực hiện lập trình bất đồng bộ. Khi bất đồng bộ thực thi tất cả dòng code cùng một lúc. -
Synchronous programming
: Tải tài nguyên một cách đơn lẻ và tuần tự, như vậy khi 1 tài nguyên trong hệ thốngcó phân cấp
không tải được, thì những tài nguyên bên dưới nó sẽ không tải được luôn. Với NodeJS, ở chế độ đồng bộ thực thi từng dòng và tiến hành thực thi dòng tiếp theo khi dòng hiện tại đã thực thi xong.
Synchronous
-
Tải tài nguyên một cách đơn lẻ và tuần tự, khi một tài nguyên trong hệ thống có phân cấp không tải được, thì những tài nguyên bên dưới nó sẽ không response.
-
Các request sẽ được hoạt động đồng bộ với giao thức đa luồng.
-
Mỗi
thread
sẽ xử lý 1 request riêng biệt, độc lập. Vì vậy, mỗi thread sẽ có 1 khoảng thời gianexecution
vàloads completely
trước khi thực hiệnevent
tiếp theo. Do đó, việcexecution
vàloads completely
trong một thread sẽblock
các thread khác, các thread khác phải đợi thread trước nó done rồi mới tới lượtexecution
vàloads completely
.
- Lập trình đồng bộ đảm bảo rằng phía client sẽ nhận được response từ request đầu tiên trước khi thực hiện request tiếp theo (FIFS). Điều này có thể dẫn đến sự chậm trễ không cần thiết và ảnh hưởng xấu đến UX.
Asynchronous
-
Trong Asynchronous Programming, một apps sẽ serve các request và response bằng cách sử dụng giao thức non-blocking I/O.
-
Khác với lập trình đồng bộ, một chương trình không đồng bộ không thực hiện các actions một cách tuần tự, thứ bậc. Vì vậy, chương trình sẽ không đợi thực hiện một request trước khi trả lời một request khác.
-
Về bản chất, nó thực hiện các request đồng thời, ngay cả khi chúng ở các chức năng khác nhau. Từ đó mang lại hiệu quả là một ứng dụng được phát triển bằng Asynchronous Programming sẽ chỉ tải toàn bộ nội dung của nó một lần.
-
Một single thread xử lý nhiều request trong một
event loop
. Vì vậy, việc một request không thành công sẽ không ảnh hưởng đến request kia.
- Vì asynchronous loading theo kiểu non-blocking, nên các ứng dụng web hoạt động theo nguyên tắc này có thể trở thành các single-page applications.
Asynchronous programming in NodeJs¶
- Một chương trình được thực thi bất đồng bộ đem lại trải nghiệm, tốc độ xử lý tốt hơn, tuy nhiên chính vì sự bất nguyên tắc, không có thứ tự thực hiện như vậy nên việc quản lý các tiến trình trở nên phức tạp và khó khăn hơn, xét ví dụ dưới đây:
const fs = require('fs');
let content;
try {
content = fs.readFileSync('file.md', 'utf-8');
} catch (err) {
console.log(err);
}
console.log(content);
-
Vấn đề ở đây là các
thread
sẽ bị block khifs.readFileSync()
đang thực thi, thực thi xong thì mới chạy tới cácthread
khác, tốn perfomance, thời gian. VD nếu có lỗi trong quá trình đọc file, phải đợi đến khi đọc file xong mới log ra được lỗi. -
Cách giải quyết ở đây là sử dụng
callback
truyền vàofs.readFileSync()
:
const fs = require('fs');
console.log('start reading a file...');
fs.readFile('file.md', 'utf-8', function (err, content) {
if (err) {
console.log('error happened during reading the file')
return console.log(err)
}
console.log(content)
});
console.log('end of the file');
- Với ví dụ này, Ta có thể dùng
Promise
để đọc nhiều file:
const fs = require('fs');
function readMultipleFile(file) {
return new Promise((resolve, reject) => {
fs.readFile(file, (err, content) => {
if (err) {
return reject(err);
}
resolve(content);
});
});
}
Promise.all([readMultipleFile("file1"), readFile("file2"), readFile("file3")])
.then((content) => console.log(content))
.catch((err) => console.log(err));
-
Việc dùng
callback
hayPromise
như trên có thể dẫn tới hiện tượng callback hell vàPromise chain
. Và tính năngAsync/Await
trong Javascript ra đời để giải quyết tình trạng trên, giúp chúng ta làm việc với các hàm bất đồng bộ ngắn gọn và dễ hiểu hơn. -
Async/Await
được xây dựng trênPromise
và tương thích với tất cả cácPromise
dựa trên API, trong đó:-
Async
- khai báo một hàm bất đồng bộ kiểu- Tự động biến đổi một hàm thông thường thành một
Promise
. - Khi gọi tới hàm
async
nó sẽ xử lý mọi thứ và được trả về kết quả trong hàm của nó. Async
cho phép sử dụngAwait
.
- Tự động biến đổi một hàm thông thường thành một
-
Await - tạm dừng việc thực hiện các hàm async.
- Khi được đặt trước một
Promise
, nó sẽ đợi cho đến khiPromise
kết thúc và trả về kết quả. Await
chỉ làm việc vớiPromise
, nó không hoạt động vớicallback
.Await
chỉ có thể được sử dụng bên trong các functionasync
.
- Khi được đặt trước một
-
const fs = require('fs');
const readFileAsync = async function (file){
try {
let content = await fs.readFile(file);
return content;
} catch (error) {
return error;
}
}
const contents = await Promise.all([readFileAsync("file1"), readFileAsync("file2"), readFileAsync("file3")]);
console.log(contents);
Memory management¶
Concepts and Memory life cycle¶
-
Mọi ứng dụng đều cần bộ nhớ để hoạt động bình thường.
Memory management
-Quản lý bộ nhớ
là quá trình phân phối dynamically cácmemory chunks
choprograms
khi chúng cần và giải phóng để thằng khác xài khiprograms
không còn cần bộ nhớ đó nữa. Việc quản lý bộ nhớ ở Application-level có thể là thủ công hoặc tự động. -
Các ngôn ngữ bậc thấp như C, quản lý bộ nhớ thủ công như
malloc()
vàfree()
. Việc quản lý bộ nhớ thủ công có thể gây ra một số lỗi lớn cho ứng dụng, chẳng hạn việc bộ nhớ bị rò rỉ khi không gian bộ nhớ đã sử dụng không bao giờ được giải phóng. -
Trong JavaScript hay NodeJs đều hỗ trợ
garbage collector
- trình thu gom rác, tự động cấp phát bộ nhớ khi các đối tượng được tạo và giải phóng nó khi chúng không được sử dụng nữa. -
Memory life cycle
: Bất kể ngôn ngữ lập trình gì,Memory life cycle
luôn giống nhau: -
Phân bổ bộ nhớ cho
process
process
sử dụng bộ nhớ được cấp phát (đọc, ghi)- Giải phóng bộ nhớ được cấp phát khi
process
không cần thiết nữa.
Memory management with Garbage Collection¶
Garbage collection
: Là cơ chế thu dọn và xóa sổ những object/giá trị không còn được dùng tới, trả lại bộ nhớ để dùng cho việc khác.GC
của V8 là một Generational Garbage Collector. Trong quá trình thực thi, các giá trị (biến, object,...) được tạo ra nằm trongMemory Heap
. V8 chiaMemory Heap
ra làm nhiều khu vực, trong đó có hai khu vực chính lànew-space
- chứa các đối tượng nhỏ, có vòng đời ngắn vàold-space
- chứa các thành phần sống dai hơn, bự hơn.
Cơ chế hoạt động:
-
Khi chúng ta khai báo một giá trị mới, giá trị này sẽ được cấp phát nằm rải rác trong khu vực
new-space
, khu vực này có một kích thước nhất định, thường là rất nhỏ (khoảng 1MB đến 8MB, tùy vào cách hoạt động của ứng dụng). Việc khai báo như thế này tạo ra nhiều khoảng trống không thể sử dụng được trong bộ nhớ. -
Khi
new-space
đầy, thì thuật toánscavenge
sẽ được kích hoạt để dọn dẹp các vùng nhớ "chết", giải phóng mặt bằng, có thể sẽ gom góp các vùng nhớ rời rạc lại gần nhau cho hợp lý. Vìnew-space
rất nhỏ, nênscavenge
được kích hoạt rất thường xuyên. -
Trong quá triển khai
scavenge
, nếu các vùng nhớ nào còn trụ lại được sau 2 chu kỳ, thì được đưa lên (promote) khu vựcold-space
, nơi mà có sức chứa lên đến hàng trăm megabytes, và là nơi mà thuật toánmark-sweep
hoặcmark-compact
hoạt động, với chu kỳ dài hơn, ít thường xuyên hơn.
Tóm lại, cơ chế GC trên đều hoạt động thông qua hai bước chính là:
-
Bước đánh dấu: thuật toán sẽ duyệt qua tất cả các giá trị có trong khu vực bộ nhớ mà nó quản lý, bước duyệt này dùng
depth-first search
, tìm gặp và đánh dấu. -
Bước xử lý: sau quá trình duyệt, tất cả những giá trị chưa được đánh dấu, sẽ bị coi là đã "chết", và sẽ bị xóa bỏ, trả lại bộ nhớ trống (sweep), hoặc gom góp lại để lấy lại các khoảng trống trong bộ nhớ không sử dụng được (compact).
Avoid Memory leaks¶
-
Lý do chính của
memory leaks
trong các ngôn ngữ có cơ chếgarbage collection
là các có cácunwanted references
- là một vùng nhớ được trỏ đến mà lại không được sử dụng trong ứng dụng nhưng vì lý do nào đó mà nó vẫn được giữ lại trong hệ thống. Khi số lượngunwanted references
nhiều, free memory không đủ để cung cấp sẽ dấn đến hượng tượngmemory leaks
. -
Trong JS, những
unwanted references
này thường là các variables nằm đâu đó trong code và nó không được sử dụng đến nhưng lại chiếm một phần của bộ nhớ. Có 3 loạimemory leaks
trong JS: -
Global variable
: Javascript có một cơ chế là đặt biến mà không cần khai báo. Ví dụ: -
Khi một biến được khai báo như trên thì JS sẽ tự động gán nó vào global object (window trên browser). Nếu như biến này chỉ hoạt động trên phạm vi
global scope
thì cũng không có sự khác biệt cho lắm. Tuy nhiên, nếu nó được định nghĩa trong một hàm thì đó lại là chuyện khác. Ví dụ: -
Nếu khai báo
bar
trong phạm vi của hàmfoo()
mà lại không sử dụngvar
để khai báo thì biếnbar
sẽ được tạo vớiglobal scope
. Khi đó tuy là chỉ khi gọi hàmfoo()
thì mới cần dùng tới biếnbar
, tuy nhiên làglobal scope
nên lúc nào ôngbar
cũng chiếm 1 phần bộ nhớ, và đây là một ví dụ điển hình vềmemory leaks
. -
Callback
vàtimer
bị lãng quên một ví dụ dẫn đến memory leak khi sử dụng setInterval -
Timer
bị treo tức khitimer
tham chiếu đến các node hoặc dữ liệu mà không còn được sử dụng nữa. Ở ví dụ trên, nếu nhưnode
bị xóa ở một lúc nào đấy thì toàn bộ đoạn code xử lý trong hàmcallback
củainterval
sẽ không cần đến nữa. Tuy nhiên, trường hợp node bị xóa trong khi interval vẫn còn hoạt động thì các vùng nhớ được sử dụng trong hàm callback của interval cũng không được giải phóng (muốn giải phóng cần dừng interval lại). Tiếp đó, các object từ bên ngoài mà được hàm callback của interval tham chiếu đến cũng không thể được giải phóng vì vẫn có thể được gọi thông qua hàm callback kia. Trường hợp này cũng dấn đếnmemory leaks
. -
Tham chiếu tới các DOM đã bị xóa
-
Khi cần lưu DOM vào một số cấu trúc dữ liệu như mảng hoặc object trong JS code để làm một loạt các tác vụ nào đó.
-
Ví dụ update dữ liệu của một vài DOM element nào đó, ta cần lưu các element này vào một mảng, object trong JS.
-
Nếu ta update xong ta xóa các DOM element cũ đi, khi đó node con (element) còn tham chiếu đến node cha (array, object) nên nó sẽ được GC coi là vẫn được tham chiếu và bỏ qua nó, từ đó có thể dẫn đến
memory leaks
. Nên cẩn thận khi tham chiếu đến các DOM. -
Closures
-
Closures
là hàm nằm trongscope
của một hàm khác có thể tham chiếu tới các biến của hàm bao nó. - Khi một scope được tạo ra cho các closures mà có cùng scope cha, chúng sẽ cùng chia sẻ scope đó. Mặc dù scope con không được gọi đến nhưng vì nó có tham chiếu đến scope cha nên nó sẽ được GC coi là vẫn đang hoạt động, từ đó có thể dẫn đến
memory leaks
.