Node.js İle Worker Threads Kullanarak Paralel İşleme Nasıl Yapılır?

Node.js İle Worker Threads Kullanarak Paralel İşleme Nasıl Yapılır?
Node.js İle Worker Threads Kullanarak Paralel İşleme Nasıl Yapılır?

Gereksinimler ve Ön Hazırlık

Worker Threads modülünü kullanabilmek için modern bir Node.js sürümüne ihtiyacınız vardır. 2026 yılı standartları gereği, en az Node.js 20.x veya daha güncel bir LTS (Long Term Support) sürümünü kullanmanızı öneririm. İşletim sisteminizde Node.js kurulu olduğunu doğrulamak için terminalde şu komutu çalıştırın:

node -v

Eğer sürümünüz 18.x veya altındaysa, resmi Node.js web sitesinden en güncel sürümü edinmeniz önerilir. Ayrıca, projenizi modüler tutmak için bir package.json dosyası oluşturup "type": "module" ayarını ekleyerek ES Modülleri (import/export) kullanmanızı tavsiye ederim.

Worker Threads Modülünü Anlamak

Node.js'de her Worker, kendi V8 motoru örneğine ve kendi olay döngüsüne sahiptir. Bu, ana iş parçacığından bağımsız olarak kod çalıştırabileceği anlamına gelir. Worker Threads, özellikle görüntü işleme, büyük veri setlerini filtreleme veya karmaşık matematiksel hesaplamalar gibi senaryolarda hayat kurtarıcıdır.

Aşağıdaki tabloda, paralel işleme yöntemlerinin kısa bir karşılaştırmasını görebilirsiniz:

Yöntem Avantaj Dezavantaj
Worker Threads Düşük bellek tüketimi, aynı bellek alanını paylaşabilme Karmaşık veri senkronizasyonu
Child Processes Tam izolasyon, çökme durumunda ana süreci etkilemez Yüksek bellek tüketimi

Adım Adım Basit Bir Worker Oluşturma

İlk adımda, ana dosyadan bir Worker başlatmayı ve bu Worker'a veri gönderip yanıt almayı öğreneceğiz. Ana dosyamız main.js, çalışacak iş parçacığı ise worker.js olacaktır.

// worker.js
import { parentPort } from 'worker_threads';

parentPort.on('message', (data) => {
  const result = data * 2; // Basit bir işlem
  parentPort.postMessage(result);
});

Yukarıdaki kod, parentPort üzerinden ana süreçten gelen mesajı bekler ve sonucu tekrar ana sürece iletir.

// main.js
import { Worker } from 'worker_threads';

const worker = new Worker('./worker.js');

worker.postMessage(10);
worker.on('message', (result) => {
  console.log('Worker sonucu:', result);
});

Bu örnekte new Worker sınıfı ile yeni bir iş parçacığı oluşturduk. postMessage ile veri gönderdik ve on('message') ile gelen cevabı yakaladık.

Veri Paylaşımı ve İletişim Protokolü

Worker'lar arasında veri aktarımı yapılırken veriler kopyalanır. Ancak çok büyük veri setlerinde kopyalama maliyeti performans kaybına yol açabilir. Bunun yerine SharedArrayBuffer kullanarak belleği paylaşabilirsiniz.

// Paylaşılan bellek örneği
const sharedBuffer = new SharedArrayBuffer(1024);
const sharedArray = new Int32Array(sharedBuffer);

// Worker'a gönderirken
worker.postMessage({ buffer: sharedBuffer });

Bu yöntem, belleğin doğrudan paylaşılmasını sağlar. Ancak dikkatli olun; aynı anda birden fazla iş parçacığı aynı bellek alanına yazmaya çalışırsa "race condition" (yarış durumu) oluşabilir. Bu tür durumlarda Atomics nesnesini kullanarak işlemleri senkronize etmelisiniz.

Güvenlik Uyarısı: Worker Threads kullanırken, dışarıdan gelen verileri mutlaka doğrulayın. Worker'lar kendi süreçlerini başlattığı için, kod enjeksiyonu veya yetkisiz dosya erişimi gibi durumlara karşı dikkatli olunmalıdır. Girdi doğrulaması için her zaman katı şemalar kullanın.

Performans Testi: CPU Yoğun İşlemler

Worker Threads'in gerçek gücünü görmek için bir asal sayı hesaplama örneği yapalım. Bu işlem tek iş parçacığında yapıldığında ana süreci tamamen dondurur.

// worker.js - Ağır hesaplama
import { parentPort } from 'worker_threads';

function isPrime(num) {
  for (let i = 2, s = Math.sqrt(num); i  1;
}

parentPort.on('message', (limit) => {
  let count = 0;
  for (let i = 2; i


Worker Threads ile Hata Ayıklama ve İzleme Stratejileri

Çoklu iş parçacıklı uygulamalarda hata ayıklama (debugging), tek iş parçacıklı Node.js uygulamalarına göre daha karmaşıktır. Worker'lar kendi yalıtılmış çalışma zamanlarına sahip oldukları için, ana süreçteki hatalar worker'ları etkilemeyebilir veya tam tersi durumlar yaşanabilir. Bu süreci yönetmek için aşağıdaki stratejileri izlemelisiniz.

Debugger Kullanımı

Node.js'in yerleşik hata ayıklayıcısını kullanırken, her bir worker için ayrı bir port açmanız gerekebilir. --inspect bayrağını kullanarak worker süreçlerini izleyebilirsiniz:

// Ana süreçte worker başlatırken hata ayıklama portunu yapılandırın
const worker = new Worker('./worker.js', {
  execArgv: ['--inspect=9229']
});

Bu yöntemle Chrome DevTools veya VS Code üzerinden her bir worker'ın bellek kullanımını ve çağrı yığınını (call stack) ayrı ayrı inceleyebilirsiniz. Ayrıca, worker içindeki hataları yakalamak için error olay dinleyicisini mutlaka tanımlayın:

worker.on('error', (err) => {
  console.error('Worker hatası:', err);
});

worker.on('exit', (code) => {
  if (code !== 0) console.error(`Worker ${code} kodu ile durdu.`);
});

Worker Threads ve Event Loop Optimizasyonu

Worker Threads, CPU yoğun işleri ana iş parçacığından (Main Thread) ayırsa da, verimli bir mimari için Event Loop'un nasıl çalıştığını anlamak kritiktir. Worker'lar ana süreçten bağımsız kendi Event Loop'larına sahiptir, ancak ana süreçle haberleşirken mesaj kuyruğunu (message queue) kullanırlar.

Mesajlaşma Yükünü Azaltma

Worker'lar ile ana süreç arasında sürekli veri gönderip almak, seri hale getirme (serialization) maliyeti nedeniyle performansı düşürebilir. Büyük veri setlerini taşımak yerine SharedArrayBuffer kullanmak, veriyi kopyalamadan paylaşmanıza olanak tanır.

Aşağıdaki tablo, veri aktarım yöntemlerinin performans karşılaştırmasını göstermektedir:

Yöntem Kullanım Durumu Performans
postMessage Küçük/Orta ölçekli JSON verileri Düşük (Kopyalama maliyeti)
SharedArrayBuffer Büyük veri dizileri (TypedArrays) Yüksek (Bellek paylaşımı)
MessagePort Worker'lar arası doğrudan iletişim Orta

İleri İpucu: Worker Pool Kullanımı

Her işlem için yeni bir worker başlatmak, kaynak tüketimi açısından maliyetlidir. Bunun yerine, uygulama başladığında belirli sayıda worker'ı hazır tutan bir "Worker Pool" (Havuz) yapısı kurmalısınız. Bu sayede worker'lar sürekli yeniden başlatılmaz, sadece görev beklerler.

// Basit bir Worker Pool mantığı
class WorkerPool {
  constructor(size) {
    this.workers = [];
    for (let i = 0; i < size; i++) {
      this.workers.push(new Worker('./task.js'));
    }
  }

  runTask(data) {
    const worker = this.workers.shift();
    worker.postMessage(data);
    this.workers.push(worker); // Havuza geri ekle
  }
}

Bu yaklaşım, özellikle yüksek trafikli API sunucularında, CPU kaynaklarını daha verimli yönetmenizi ve ani yük artışlarında sistemin kilitlenmesini engellemenizi sağlar.

Bu yazıya tepkinizi paylaşın:
Deniz Aydın

On yıldır dijital yayıncılıkta pratik çözüm rehberleri hazırlıyorum. Karmaşık süreçleri herkesin anlayabileceği adım adım yönergelere dönüştürme konusunda uzmanım.

Yorumlar (0)

Yorum Yaz