Node.js Söylendiği Gibi Tek Çekirdekte mi Çalışıyor?
Evet, Node.js varsayılan olarak tek çekirdekte çalışır, çünkü çalışma modeli event-driven (olay güdümlü) ve non-blocking I/O (engelleyici olmayan girdi/çıktı) prensiplerine dayanır. Node.js, JavaScript’in tek iş parçacıklı bir dil olmasına uygun olarak tek bir olay döngüsüyle (event loop) çalışır. Ancak bu, Node.js’nin çok çekirdekli bir işlemciyi hiç kullanmayacağı anlamına gelmez. Şimdi bunu detaylandıralım.
Node.js’in Tek Çekirdekte Çalışma Mantığı
- Event Loop: Node.js, tek bir iş parçacığında çalışan bir olay döngüsü kullanır. Bu, aynı anda birden fazla işlem yapabilmek için engelleyici olmayan (non-blocking) I/O işlemlerine ve geri çağırma fonksiyonlarına dayanır.
- Asenkron I/O: Ağ işlemleri, dosya sistemine erişim gibi I/O operasyonları, Node.js tarafından işletim sistemine delege edilir ve işlemin tamamlanması için beklenmez. Böylece, aynı iş parçacığı başka işleri gerçekleştirebilir.
Çok Çekirdekli İşlemciler ve Node.js
Node.js tek çekirdekte çalışsa da çok çekirdekli işlemcilerden faydalanmak için farklı yöntemler vardır:
- Cluster Modülü
1- Node.js, her çekirdek için birer süreç (process) oluşturmak için ‘cluster’ modülünü sağlar.
2- Her süreç (child process), ana sürecin (master process) bir kopyası gibi çalışır ve aynı uygulamayı çalıştırabilir.
3- Bu yaklaşım sayesinde her süreç farklı çekirdeklerde çalışır ve yük paylaşımı sağlanır.
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
console.log(`Master process ${process.pid} is running`);
// Her çekirdek için bir çalışan oluştur
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`Worker ${worker.process.pid} died`);
});
} else {
// İşçi süreçler burada sunucuyu çalıştırır
http.createServer((req, res) => {
res.writeHead(200);
res.end('Hello World\n');
}).listen(8000);
console.log(`Worker ${process.pid} started`);
}
- Worker Threads
1- Daha yeni bir özellik olan ‘worker_threads’ modülü, Node.js uygulamasında aynı işlem için farklı iş parçacıkları oluşturmayı sağlar.
2- Özellikle CPU yoğunluklu işlemler için faydalıdır, çünkü event loop’u bloke etmeden paralel hesaplamalar yapılabilir.
3- Bu yöntem, child_process veya cluster kullanmak yerine daha hafif ve hızlıdır.
const { Worker } = require('worker_threads');
function runService(workerData) {
return new Promise((resolve, reject) => {
const worker = new Worker('./worker.js', { workerData });
worker.on('message', resolve);
worker.on('error', reject);
worker.on('exit', (code) => {
if (code !== 0) {
reject(new Error(`Worker stopped with exit code ${code}`));
}
});
});
}
runService({ data: 'Test Data' })
.then(result => console.log(result))
.catch(err => console.error(err));
Next.js Projemize Worker Threads Modülünü Eklemek
worker_threads modülünü Next.js projenize entegre etmek, özellikle CPU yoğunluklu işlemler veya paralel hesaplama gerektiren durumlarda faydalıdır. Ancak, Next.js sunucu tarafında (server-side) çalışan bir çerçeve olduğu için bu entegrasyon yalnızca sunucu tarafında geçerlidir (örneğin, API rotaları veya getServerSideProps gibi sunucu tarafı metodları).
1. Worker Dosyasını Oluşturun
‘worker_threads’ modülü, her işçinin (worker) ayrı bir dosyada çalışmasını gerektirir. Bu dosya işçinin gerçekleştireceği görevi içerir.
// workers/heavyTask.js
const { parentPort, workerData } = require('worker_threads');
// Ağır işlem (örneğin, Fibonacci hesaplaması)
function heavyComputation(n) {
if (n <= 1) return n;
return heavyComputation(n - 1) + heavyComputation(n - 2);
}
// Veriyi işleyip sonucu gönder
const result = heavyComputation(workerData);
parentPort.postMessage(result);
2. API Rotasında Worker Kullanımı
Next.js API rotaları sunucu tarafında çalışır, bu nedenle ‘worker_threads’ doğrudan kullanılabilir.
// app/api/compute/route.js
import { Worker } from 'worker_threads';
import path from 'path';
export async function POST(request) {
const body = await request.json();
const { number } = body;
// Worker işlevini Promise ile sarmalayarak sonucu bekleyin
const runWorker = (workerData) => {
return new Promise((resolve, reject) => {
const workerPath = path.resolve('./workers/heavyTask.js'); // Worker dosyasının yolu
const worker = new Worker(workerPath, { workerData });
worker.on('message', resolve); // İşlem tamamlandığında sonucu alın
worker.on('error', reject); // Hata durumunda reddedilir
worker.on('exit', (code) => {
if (code !== 0) {
reject(new Error(`Worker stopped with exit code ${code}`));
}
});
});
};
try {
const result = await runWorker(number); // Worker'ı çalıştır ve sonucu bekle
return new Response(JSON.stringify({ result }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
} catch (error) {
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
headers: { 'Content-Type': 'application/json' },
});
}
}
3. Client Tarafında İstek Gönderin
Client tarafında bir form veya bir buton ile API’ye veri gönderip sonucu alın.
'use client';
import { useState } from 'react';
export default function Home() {
const [number, setNumber] = useState(10);
const [result, setResult] = useState(null);
const [loading, setLoading] = useState(false);
const handleSubmit = async () => {
setLoading(true);
setResult(null);
try {
const response = await fetch('/api/compute', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ number }),
});
const data = await response.json();
setResult(data.result);
} catch (error) {
console.error('Error:', error);
} finally {
setLoading(false);
}
};
return (
<div>
<h1>Heavy Computation with Worker Threads</h1>
<input
type="number"
value={number}
onChange={(e) => setNumber(Number(e.target.value))}
/>
<button onClick={handleSubmit} disabled={loading}>
{loading ? 'Calculating...' : 'Calculate'}
</button>
{result !== null && <p>Result: {result}</p>}
</div>
);
}
4. Önemli Noktalar
Path Ayarları:
- path.resolve(‘./workers/heavyTask.js’), Node.js’nin process.cwd() (çalışma dizini) üzerinden çözülür.
- Eğer next dev sırasında doğru çalışmıyorsa, tam yolu belirtmek için import.meta.url veya diğer yöntemleri kullanabilirsiniz.
const workerPath = path.join(process.cwd(), 'workers', 'heavyTask.js');
Serverless Ortam:
- Eğer uygulamanız serverless ortamda çalışıyorsa, worker_threads modülü uyumsuz olabilir. Böyle bir durumda alternatif paralel işleme hizmetleri düşünmelisiniz.
Error Handling:
- API rotasında kapsamlı hata yönetimi eklemek performansı ve güvenilirliği artırır.
Beni LinkedIn’den takip edebilirsiniz.
Bir önceki yazımı “Bilmen Gereken 15 Git Komutu” okudunuz mu?