자바스크립트 개발자라면 동시성과 병렬성의 개념을 이해하는 것이 매우 중요합니다. 이 글에서는 이 두 개념의 차이점, 자바스크립트에서의 구현 방법, 그리고 관련 이론 및 용어를 정리해보겠습니다.
동시성 (Concurrency)
동시성은 여러 작업이 동시에 실행되는 것처럼 보이는 상태를 말합니다. 자바스크립트는 기본적으로 단일 스레드로 동작하기 때문에 실제로 여러 작업을 동시에 수행할 수는 없습니다. 대신, 비동기 작업을 통해 여러 작업이 동시에 처리되는 것처럼 구현할 수 있습니다.
동시성 예제
아래 코드는 setTimeout
과 Promise
를 사용하여 동시성을 구현한 예시입니다.
console.log("작업 1 시작");
setTimeout(() => {
console.log("작업 1 완료");
}, 1000);
console.log("작업 2 시작");
new Promise((resolve) => {
setTimeout(() => {
resolve("작업 2 완료");
}, 500);
}).then((message) => console.log(message));
위 코드에서는 작업 1
과 작업 2
가 동시에 진행되는 것처럼 보입니다. 실제로는 비동기적으로 처리되고 있습니다.
병렬성 (Parallelism)
병렬성은 여러 작업이 실제로 동시에 실행되는 것을 의미합니다. 이는 멀티 스레드나 멀티 프로세스를 사용하여 구현됩니다. 자바스크립트는 기본적으로 단일 스레드이지만, 웹 워커(Web Worker)를 사용하면 별도의 스레드에서 작업을 실행할 수 있습니다.
병렬성 예제
웹 워커를 사용하는 예시는 다음과 같습니다.
// worker.js
self.onmessage = function(e) {
let result = 0;
for (let i = 0; i < e.data; i++) {
result += i;
}
postMessage(result);
};
// main.js
const worker = new Worker('worker.js');
worker.postMessage(1000000);
worker.onmessage = function(e) {
console.log("결과:", e.data);
};
위 코드에서 worker.js
는 별도의 스레드에서 실행되며, CPU 집약적인 작업을 메인 스레드와 분리하여 처리합니다.
추가로 알아야 할 이론 및 용어
Event Loop
자바스크립트의 비동기 처리 메커니즘을 이해하는 데 중요한 개념입니다. 이벤트 루프는 콜백 큐와 마이크로태스크 큐를 관리하여 비동기 작업이 완료되면 관련된 콜백 함수를 실행합니다.
Promise
비동기 작업의 결과를 나타내는 객체로, 성공 또는 실패의 상태를 나타냅니다. 다음은 Promise의 사용 예시입니다.
const fetchData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("데이터 수신 완료");
}, 1000);
});
};
fetchData().then(data => console.log(data));
async/await
비동기 코드를 더 간결하게 작성할 수 있도록 도와주는 구문입니다. Promise를 기반으로 하며, I/O 작업이 완료될 때까지 기다릴 수 있습니다.
const fetchData = async () => {
const data = await new Promise((resolve) => {
setTimeout(() => {
resolve("데이터 수신 완료");
}, 1000);
});
console.log(data);
};
fetchData();
Web Worker
별도의 스레드에서 JavaScript 코드를 실행할 수 있게 해주는 API입니다. CPU 집약적인 작업을 메인 스레드와 분리하여 성능을 향상시킬 수 있습니다.
// main.js
const worker = new Worker('worker.js');
worker.postMessage({ data: [1, 2, 3, 4, 5] });
worker.onmessage = function(event) {
console.log('계산 결과:', event.data);
};
// worker.js
self.onmessage = function(event) {
const result = event.data.data.reduce((sum, num) => sum + num, 0);
self.postMessage(result);
};
Web Worker를 사용하여 배열의 합계를 계산하고 있습니다. 이렇게 하면 메인 스레드가 차단되지 않습니다.
스레드와 프로세스
프로세스
운영 체제에서 실행되고 있는 프로그램의 인스턴스입니다. 각 프로세스는 독립된 메모리 공간을 가지며, 서로 간섭하지 않고 실행됩니다.
스레드
프로세스 내에서 실행되는 가장 작은 실행 단위입니다. 여러 스레드는 같은 프로세스 내에서 메모리와 자원을 공유할 수 있습니다.
스레드 및 프로세스 생성
자바스크립트에서는 기본적으로 단일 스레드로 동작하지만, 웹 워커를 사용하여 새로운 스레드를 생성할 수 있습니다. Node.js 환경에서는 child_process
모듈을 통해 새로운 프로세스를 생성할 수 있습니다.
I/O 알림
I/O 알림은 비동기 작업이 완료되었을 때 이를 알리는 메커니즘입니다. 자바스크립트에서는 이벤트 기반 모델과 Promise를 사용하여 이 작업을 처리합니다.
I/O 알림 예시
const fs = require('fs');
// 이벤트 기반 모델
fs.readFile('example.txt', 'utf8', (err, data) => {
if (err) throw err;
console.log(data);
});
// Promise 사용
const readFilePromise = () => {
return new Promise((resolve, reject) => {
fs.readFile('example.txt', 'utf8', (err, data) => {
if (err) reject(err);
resolve(data);
});
});
};
readFilePromise().then(console.log).catch(console.error);
동시성과 병렬성의 주의
1. 비동기 작업의 순서 보장
console.log('시작');
setTimeout(() => {
console.log('첫 번째 타이머');
}, 0);
setTimeout(() => {
console.log('두 번째 타이머');
}, 0);
console.log('끝');
모든 동기 코드가 실행된 후, 이벤트 루프는 이벤트 큐에 있는 작업을 확인합니다. 이 경우, 두 개의 타이머가 0ms 후에 실행될 것으로 예약되어 있지만, 실제로는 현재 실행 중인 스크립트가 끝난 후에야 실행됩니다.
최종 출력 예시
시작
끝
첫 번째 타이머
두 번째 타이머
즉, 비동기적으로 예약된 함수들은 현재 실행 중인 코드가 모두 완료된 후에 실행됩니다. 이는 자바스크립트가 단일 스레드로 작동하기 때문에 발생하는 현상입니다.
2. 비동기 작업의 올바른 처리
Promise나 async/await를 사용하여 비동기 작업을 올바르게 처리해야 합니다.
function fetchData() {
return new Promise(resolve => {
setTimeout(() => resolve('데이터'), 1000);
});
}
async function processData() {
try {
console.log('데이터 요청 시작');
const data = await fetchData();
console.log('받은 데이터:', data);
} catch (error) {
console.error('에러 발생:', error);
}
}
processData();
async/await를 사용하여 비동기 작업을 동기적으로 보이게 처리하고 있습니다.
- processData()가 호출되면 "데이터 요청 시작"이 출력됩니다.
- 이후 fetchData()가 호출되고, 1초 동안 대기합니다.
- 1초 후, "데이터"가 반환되고 "받은 데이터: 데이터"가 출력됩니다.
최종 출력 예시
데이터 요청 시작
(1초 후)
받은 데이터: 데이터
3.경쟁 상태(Race Condition) 방지
여러 비동기 작업이 동시에 실행될 때 경쟁 상태가 발생할 수 있습니다.
let count = 0;
function incrementCount() {
setTimeout(() => {
const currentCount = count;
count = currentCount + 1;
console.log('Count:', count);
}, Math.random() * 10);
}
for (let i = 0; i < 5; i++) {
incrementCount();
}
이 코드는 count 변수에 대한 경쟁 상태를 일으킬 수 있습니다. 결과가 예상과 다를 수 있습니다.
- incrementCount()가 호출될 때마다, 각각의 비동기 작업이 0ms에서 10ms 사이의 시간 후에 실행되도록 예약됩니다. 이로 인해, 각 작업이 동시에 실행되지 않더라도, 모든 작업이 비동기적으로 이벤트 큐에 추가됩니다.
- 각 비동기 작업은 count의 현재 값을 읽고, 1을 더한 후에 그 값을 다시 count에 할당합니다.
- 이 과정에서 여러 작업이 count의 값을 읽고 동시에 업데이트하기 때문에, 최종적으로 출력되는 count의 값은 실행 순서에 따라 달라질 수 있습니다.
각 incrementCount 호출이 비동기적으로 실행되므로, 콘솔 출력의 순서는 다음과 같이 다양할 수 있습니다.
Count: 1
Count: 2
Count: 3
Count: 4
Count: 5
또는, 만약 값이 중복되어 읽히는 경우:
Count: 1
Count: 1
Count: 2
Count: 3
Count: 4
결론
동시성과 병렬성은 자바스크립트에서 비동기 프로그래밍을 이해하는 데 필수적입니다. 이 두 개념의 차이를 명확히 이해하고, 적절한 상황에서 각각의 방법을 활용하는 것이 중요합니다. 자바스크립트의 비동기 처리 메커니즘인 이벤트 루프, Promise, async/await, 웹 워커 등을 잘 활용하여 효율적인 코드를 작성해 보세요.
'Language > JavaScript' 카테고리의 다른 글
Tagged Template Literals (0) | 2024.11.21 |
---|---|
document.createDocumentFragment를 활용한 성능 최적화 (1) | 2024.11.20 |
JavaScript의 Fetch 메서드는 왜 await를 두 번 사용하는가? (6) | 2024.11.11 |
JavaScript로 API 호출 어떻게 하는 건데? (0) | 2024.05.06 |
[JavaScript] 날씨 앱 만들기(OpenWeatherMap API) (0) | 2024.05.05 |