.NET Aslında Çok Basit — Bölüm 2: Paralel Programlama
Hepinize tekrar selamlar.
.NET Aslında Çok Basit — Bölüm 2: Paralel Programlama
Hepinize tekrar selamlar.
.NET Aslında Çok Basit serisinin ilk yazısında asenkron programlama çerçevesinde bazı kritik noktaları netleştirmiştik:
- Async demek paralel demek değildir
- Async = bir iş beklenirken thread’i serbest bırakmaktır
- I/O-bound işler için idealdir
- CPU’yu hızlandırmaz
Bu yazıda ise gerçekten CPU’yu konuşturduğumuz yere geliyoruz: Paralel programlama.
Hadi gelin; doğru kullanıldığında ciddi performans artışı sağlayan ancak yanlış kullanıldığında ise production ortamında yangın çıkarabilen bu metodolojiye temizinden giriş yapalım.
Başlamadan önce yine en kritik ayrımı netleştirelim.
Async ile Paralel Aynı Şey Değil
Yazılım dünyasında en sık karıştırılan cümlelerden biri:
“Async yaptık ya, zaten paralel çalışmıyor mu?”
Cevap: Maalesef hayır.
Async ne yapıyordu?
I/O işlemlerini beklerken thread’i boşa çıkarıyordu. (I/O işlemlerin ne olduğunı hatırlamak için önceki yazıma göz atabilirsiniz.)
Yani bir HTTP isteği, veritabanı sorgusu ya da dosya okuma sırasında thread kapıda beklemiyordu. ThreadPool’a geri dönüp başka işler alabiliyordu.
Bu bize ne kazandırıyordu?
- Daha iyi ölçeklenebilirlik
- Daha az thread tıkanması
- Daha sağlıklı yük yönetimi
Ama CPU hızlanmıyordu çünkü CPUluk bir durum yoktu.
Paralel Ne Yapar?
Paralel programlama şunu yapar:
CPU core’larını kullanarak gerçekten aynı anda birden fazla işi çalıştırır.
Buradaki anahtar kelime: gerçekten aynı anda.
İki HTTP isteğini aynı anda başlatmak çoğu zaman paralel CPU değildir. Bu genelde I/O concurrency’dir.
Paralellik asıl anlamını şurada bulur:
- Büyük hesaplamalar
- Veri işleme
- Matematiksel yoğun operasyonlar
- CPU’yu terleten işler
Yani CPU-bound senaryolarda.
CPU Core Gerçeği
Bilgisayarınızda büyük ihtimalle:
- 4 core
- 8 core
- 16 core
var.
Her core aynı anda bir işi çalıştırabilir.
Şu kodu düşünelim:
long sum = 0;
for (int i = 0; i < 1_000_000_000; i++)
{
sum += i;
}
Bu kod:
- Tek thread
- Tek core
- Uzun süre
demektir.
Ama bu işi 8 parçaya bölüp 8 core’a dağıtırsak?
İşte burada paralel programlama devreye girer.
Minion Ofisi — Paralel Versiyon

Önceki yazıda:
- Thread = Minion
- ThreadPool = Ofis
demiştik. Bu metaforları koruyarak şimdi düşünelim:
Ofiste 8 minion var. Elimizde dedev bir hesaplama işi var.
Async ne yapıyordu?
“Bir iş beklenirken minion’u kilitleme, ofise (threadpool) geri gönder. Cevap geldiğinde ofisteki msüsait bir minion’ı gönderip cevabı al ve işlemi devam ettir.”
Paralel ne yapar?
“Bu işi 8 parçaya böl. Her minion bir parçasını aynı anda hesaplasın.”
Bu sefer gerçekten herkes çalışıyor.
Parallel.For — Ne İçin Kullanılır?
Parallel.For, bir for döngüsünü birden fazla core üzerinde paralel çalıştırmak için kullanılır.
Amaç:
Büyük, CPU-bound ve birbirinden bağımsız işleri parçalara bölerek aynı anda çalıştırmak.
Önemli kurallar:
- CPU-bound işler için uygundur
- I/O işleri için uygun değildir
- Iterasyonlar birbirinden bağımsız olmalıdır
- Ortak değişken varsa thread-safety gerekir
Klasik Hata
long sum = 0;
Parallel.For(0, 1_000_000_000, i =>
{
sum += i; // Tehlikeli
});
Bu kod ilk bakışta doğru gibi görünür.
Ama burada ciddi bir problem vardır;
Race Condition
Birden fazla thread aynı anda sum değişkenini değiştirmeye çalışır.
sum++ aslında 3 adımdır:
- Değeri oku
- +1 yap
- Geri yaz
İki thread aynı anda okuyup yazarsa biri diğerini ezer.
Beklenen sonuç: 1000 Gerçek sonuç: Rastgele bir sayı.
İşte buna race condition denir.
Thread-Safe Ne Demek?
Thread-safe demek:
Aynı kod birden fazla thread tarafından aynı anda çalıştırıldığında bile doğru ve tutarlı sonuç üretmesi demektir.
Tek thread ile çalışan kod güvenli olabilir. Ama 8 thread ile çalıştırdığında bozuluyorsa thread-safe değildir.
Thread-Safe Nasıl Sağlanır?
1) Interlocked
int counter = 0;
Parallel.For(0, 1000, i =>
{
Interlocked.Increment(ref counter);
});
Bu artırma işlemi atomik yapılır. Kimse araya giremez.
2) lock
object locker = new object();
Parallel.For(0, 1000, i =>
{
lock (locker)
{
counter++;
}
});
Aynı anda sadece bir thread içeri girebilir.
Thread-safe olur ama paralellik ve buna bağlı olarak performans azalır.
3) Concurrent Koleksiyonlar
Normal Dictionary:
var dict = new Dictionary<int, int>();
Parallel.For(0, 1000, i =>
{
dict[i] = i; // Güvensiz
});
Exception fırlatabilir. Internal state bozulabilir.
Çünkü Dictionary:
- Resize yapar
- Rehash yapar
- Internal array’leri değiştirir
Ve bunlar thread-safe değildir.
Concurrent Dictionary:
var dict = new ConcurrentDictionary<int, int>();
Parallel.For(0, 1000, i =>
{
dict[i] = i;
});
Bu güvenlidir.
Neden?
- Internal lock striping kullanır
- Atomic operasyonlar içerir
Lock Striping
Normal lock: “Bir thread girdi mi herkes beklesin.”
ConcurrentDictionary:
- Koleksiyonu segmentlere böler
- Her segment için ayrı lock tutar
Yani bir thread bucket 1’de çalışırken diğeri bucket 50’de çalışabilir.
GetOrAdd — Hayat Kurtaran Metot
Yanlış yaklaşım:
if (!dict.ContainsKey(key))
{
dict[key] = ComputeValue();
}
Paralel ortamda iki thread aynı anda çalışabilir. Bu da threadlerin aynı anda veriyi değiştirmeye çalışmasına veya aynı anda veri zaten var diyip çıkmasına sebebiyet verebilir.
Doğru yaklaşım:
var value = dict.GetOrAdd(key, k => ComputeValue());
Bu atomiktir.
Aynı anda 10 thread çağırsa bile ComputeValue yalnızca 1 kez çalışır.
Task.Run ile Paralel Çalıştırmak
var task1 = Task.Run(() => HeavyCalculation1());
var task2 = Task.Run(() => HeavyCalculation2());
var task3 = Task.Run(() => HeavyCalculation3());
await Task.WhenAll(task1, task2, task3);
Burada:
- 3 CPU-bound iş
- 3 ayrı thread
- Gerçek paralellik
Burada await Neden Var?
Önceki yazıda await’in thread’i serbest bıraktığını söylemiştik.
Burada ise farklı bir rolü var.
Task.Run ne yapıyor?
ThreadPool’dan thread alıyor ve işi gerçekten çalıştırıyor. CPU aktif olarak hesap yapıyor.
await Task.WhenAll ne yapıyor?
Şunu söylüyor:
“Bu üç işi başlattım. Hepsi bitmeden devam etmeyeceğim.”
Bu bir join davranışıdır.
I/O’da await:
- Kaynak tasarrufu sağlar
CPU’da await:
- İşlerin bitmesini bekler
- Hız kazancı core’lardan gelir
WhenAll Savaşları: I/O-bound WhenAll vs CPU-bound WhenAll
I/O-bound
var t1 = httpClient.GetStringAsync(url1);
var t2 = httpClient.GetStringAsync(url2);
var t3 = httpClient.GetStringAsync(url3);
await Task.WhenAll(t1, t2, t3);
Kazanç:
3 + 3 + 3 saniye yerine en yavaş olan kadar beklersin.
Bu concurrency’dir.
CPU-bound
var t1 = Task.Run(() => HeavyCalculation1());
var t2 = Task.Run(() => HeavyCalculation2());
var t3 = Task.Run(() => HeavyCalculation3());
await Task.WhenAll(t1, t2, t3);
Kazanç:
İşler aynı anda core’larda çalışır. Toplam süre yaklaşık max(t1, t2, t3) olur.
Bu gerçek paralelliktir.
En Tehlikeli Karışım
await Task.WhenAll(urls.Select(url =>
Task.Run(() => httpClient.GetStringAsync(url).Result)
));
Burada:
- Task.Run ile gereksiz thread açılıyor
- .Result ile thread bloklanıyor
Sonuç:
- ThreadPool tükenir
- Latency artar
- Sistem yavaşlar
Paralel Her Zaman Daha Hızlı Değildir
Paralel programlama:
- Context switching maliyeti
- Senkronizasyon maliyeti
- Memory contention
- Cache invalidation
getirir. Küçük işler için paralel yapmak genelde daha yavaştır. Neticede paralelizm, bedava performans değildir.
Async + Paralel Doğru Kombinasyon
var files = await Task.WhenAll(
urls.Select(url => httpClient.GetByteArrayAsync(url))
);
var results = files
.AsParallel()
.Select(ProcessFile)
.ToList();
Burada:
- Async I/O’yu yönetti
- Paralel CPU’yu kullandı
İdeal kombinasyon budur.
Özet
Async:
- I/O beklerken thread’i serbest bırakır
- Ölçeklenebilirlik sağlar
Paralel:
- CPU core’larını kullanır
- Hesaplama süresini düşürür
En tehlikeli cümle:
“Async yaptık, zaten paralel çalışıyor.”
Hayır.
Async sistemi boğulmadan çalıştırır. Paralel CPU’yu hızlandırır.
KAPANIŞ

Paralel programlama, .NET’te ilk bakışta büyülü gibi görünür.
Aynı anda birden fazla iş. Birden fazla core. Daha kısa sürede tamamlanan hesaplamalar.
Ama işin özü aslında çok basittir:
Paralel programlama, CPU-bound işleri parçalara bölüp gerçekten aynı anda çalıştırmaktır.
Ne daha fazlası, ne daha azı.
Bu yazıda gördük ki:
- Async ile paralel aynı şey değildir.
- Task.WhenAll bazen concurrency’dir, bazen gerçek paralelliktir.
- Parallel.For güçlüdür ama bilinçsiz kullanıldığında tehlikelidir.
- Thread-safety olmadan paralel kod yazmak, rastgele bug üretmektir.
Paralellik performans kazandırır. Ama kontrol edilmezse kaos üretir.
Eğer bu yazıdan sonra:
- CPU-bound ile I/O-bound ayrımını net görüyorsanız,
- Parallel.For yazmadan önce “Bu kod thread-safe mi?” diye düşünüyorsanız,
- Task.Run eklerken gerçekten CPU işi olup olmadığını sorguluyorsanız,
- ConcurrentDictionary kullanırken mantıksal yarışları da hesaba katıyorsanız,
o zaman bu yazı amacına ulaşmış demektir. Bir sonraki yazımızda paralel programlamanın derinlerine inerek bu konuya noktayı koymayı hedefliyorum. “ne zaman patlar?” kısmı olacak.
Bir sonraki bölümde;
- Race condition’ı canlı bir örnekle adım adım inceleyeceğiz
- Deadlock nasıl oluşur, nasıl fark edilir göreceğiz
- Lock, Interlocked ve immutable yapıların gerçek rolünü konuşacağız
- ThreadPool starvation neden olur anlayacağız
- Sınırsız Task.WhenAll neden production’da sistemi kilitler göreceğiz
Çünkü paralel kod yazmak zor değildir. Zor olan, güvenli paralel kod yazmaktır.
Bir sonraki yazıda bu güvenlik katmanını birlikte inşa edeceğiz.
O zamana kadar minionları doğru yönlendirin, kendinize iyi bakın, hoşçakalın!
메타데이터
- post_id
- 76d6887d9f6c
- slug
- net-aslında-çok-basit-bölüm-2-paralel-programlama-76d6887d9f6c
- url
- https://medium.com/@fethitekyaygil/net-asl%C4%B1nda-%C3%A7ok-basit-b%C3%B6l%C3%BCm-2-paralel-programlama-76d6887d9f6c
- canonical_url
- https://medium.com/@fethitekyaygil/net-asl%C4%B1nda-%C3%A7ok-basit-b%C3%B6l%C3%BCm-2-paralel-programlama-76d6887d9f6c
- author_url
- https://medium.com/@fethitekyaygil
- status
- ok
- fetched_at
- 2026-07-24 19:22:03