Nesne Tabanlı Programlama 2

12 - Concurrency, Paralellik ve Asenkron Programlama

Emre Can Yılmaz

Ondokuz Mayıs Üniversitesi

2025

Giriş

Bu hafta, programlarımızı daha verimli ve hızlı hale getirebilmek için kullanabileceğimiz üç önemli kavramı ele alacağız: Eş zamanlılık (concurrency), paralellik (parallelism) ve asenkron programlama (asynchronous programming).

Bu kavramlar sayesinde, bir programın aynı anda birden fazla işi gerçekleştirmesi ya da beklemeden diğer işlemlere geçebilmesi mümkün hale gelir. Özellikle çok çekirdekli işlemcilerin yaygınlaştığı günümüzde bu yetenekler büyük önem taşır.

Bu haftaki öğrenme hedeflerimiz:

  • Thread ve process kavramlarını tanımak,
  • Thread-safe yapılara neden ihtiyaç duyulduğunu anlamak,
  • Python’da threading, multiprocessing ve asyncio kütüphanelerinin temel kullanımını öğrenmek,
  • Basit uygulamalarla bu kavramları pekiştirmek.

Bu konular, özellikle büyük ve kullanıcı etkileşimli yazılımlarda performans artırmak için gereklidir. Ders boyunca, hem teorik bilgileri hem de uygulamalı örnekleri birlikte göreceğiz.

Temel Kavramlar

Eş Zamanlılık (Concurrency)

  • Bir programda birden fazla işin sanki aynı anda yapılmasıdır.
  • Örnek: Bir metin editöründe yazarken otomatik kaydetmenin arka planda çalışması.
  • Python: threading modülü kullanılır.

Paralellik (Parallelism)

  • Gerçekte birden fazla işin aynı anda yapılmasıdır.
  • Çok çekirdekli işlemciler sayesinde mümkün olur.
  • Python: multiprocessing modülü kullanılır.

Asenkron Programlama

  • Kodun “beklemeden” ilerlemesini sağlar.
  • Örnek: Web’den veri çekme işlemi devam ederken arayüzün donmaması.
  • Python: asyncio modülü kullanılır.

Threading Nedir?

Threading, aynı program içinde birden fazla iş parçacığının (thread) çalışmasını sağlayan bir tekniktir. Her thread, aynı anda farklı bir işlemi gerçekleştirebilir gibi çalışır. Bu, özellikle I/O-bound (yoğun) işlemlerde performans artırabilir.

Threading Kullanımı

import threading

def yaz():
    for i in range(5):
        print("Yazma işlemi")

def oku():
    for i in range(5):
        print("Okuma işlemi")

thread1 = threading.Thread(target=yaz)
thread2 = threading.Thread(target=oku)

thread1.start()
thread2.start()

thread1.join()
thread2.join()

Açıklama: İki farklı iş parçacığı (thread) aynı anda çalışıyor. start() ile başlatılır, join() ile tamamlanması beklenir.

Alıştırma:

  • Az önceki kodu değiştirerek 3 farklı thread ile farklı mesajlar yazdırın.

Threading’de Zamanlama Sorunu (Race Condition)

Thread’ler aynı anda paylaşılan bir veriyi değiştirirse, beklenmedik sonuçlar ortaya çıkabilir. Buna “race condition” denir. Aşağıdaki örnekte, birden fazla thread aynı anda counter değişkenini artırmakta ve sonuç her zaman doğru çıkmamaktadır.

Soru 1: Hatalı Kodu Düzeltme

import threading

class Counter:
    def __init__(self):
        self.counter = 0

    def increment(self):
        self.counter += 1

counter = Counter()

def worker():
    for _ in range(1000):
        counter.increment()

threads = [threading.Thread(target=worker) for _ in range(10)]
for thread in threads:
    thread.start()
for thread in threads:
    thread.join()

print(counter.counter)  # Beklenen: 10000, ama her zaman doğru değil!

Çözüm: Kilit (Lock) Kullanarak Koruma

import threading

class Counter:
    def __init__(self):
        self.counter = 0
        self.lock = threading.Lock()

    def increment(self):
        with self.lock:
            self.counter += 1

...

Açıklama: with self.lock ifadesi sayesinde aynı anda sadece bir thread counter değerini değiştirebilir.

3. Thread-Safe Nedir?

  • Aynı veriye birden fazla thread erişiyorsa veri bozulabilir.
  • Bu durumu engellemek için kilit (lock) kullanılır.
import threading

sayi = 0
kilit = threading.Lock()

def arttir():
    global sayi
    for _ in range(100000):
        with kilit:
            sayi += 1

threadler = []
for _ in range(10):
    t = threading.Thread(target=arttir)
    threadler.append(t)
    t.start()

for t in threadler:
    t.join()

print(sayi)

Açıklama: with kilit: sayesinde aynı anda sadece bir thread sayi değişkenini değiştiriyor.

Alıştırma:

  • Kilit kullanmadan aynı kodu çalıştırın, farkı gözlemleyin.

Paralellik ve multiprocessing

Multiprocessing, her biri kendi belleğine sahip olan farklı işlemler (process) ile çalışarak gerçek paralellik sağlar. Bu yöntem CPU-bound (yoğun) işlemlerde daha verimlidir.

import multiprocessing

def carp(x):
    return x * x

if __name__ == '__main__':
    with multiprocessing.Pool(processes=4) as pool:
        sonuc = pool.map(carp, [1, 2, 3, 4, 5])
        print(sonuc)

Açıklama: map fonksiyonu listedeki her bir eleman için farklı bir process çalıştırır.

Soru 2: Multiprocessing ile Kare Hesaplama

1’den 100’e kadar sayıların karelerini hesaplayan bir program yazın. Her işlem, 10 sayının karelerini hesaplasın.

from multiprocessing import Process

def calculate_squares(start, end):
    result = []
    for i in range(start, end + 1):
        result.append(i * i)
    print(f"{start}-{end} arası kareler: {result}")

if __name__ == "__main__":
    processes = []
    for i in range(1, 101, 10):
        p = Process(target=calculate_squares, args=(i, i + 9))
        processes.append(p)
        p.start()

    for p in processes:
        p.join()

print("Tüm işlemler bitti!")

Açıklama: Her işlem, 10’ar sayının karelerini hesaplar ve paralel çalışır.

Asenkron Kodlama: asyncio

asyncio, asenkron görevlerin aynı anda yürütülmesini sağlayan bir Python kütüphanesidir. await ifadesiyle belirtilen işlemler beklenirken başka işler yapılabilir, bu da özellikle I/O-bound (yoğun) işlemler için avantaj sağlar.

import asyncio

async def selamla():
    print("Merhaba")
    await asyncio.sleep(1)
    print("Naber?")

async def main():
    await asyncio.gather(selamla(), selamla())

asyncio.run(main())

Açıklama: Aynı anda iki selamla fonksiyonu başlatılıyor. sleep sırasında diğeri çalışabiliyor.

Alıştırma:

  • 2 farklı asenkron fonksiyon tanımlayın ve asyncio.gather() ile birlikte çalıştırın.

Gerçek Dünya Senaryoları

  • Web sunucuları: Aynı anda binlerce istemciye cevap verir.
  • Oyun motorları: Fizik, ses ve grafik işlemleri farklı threadlerde yürütülür.
  • Veri işleme: Çok çekirdekli sistemlerde büyük veriler paralel işlenebilir.

Quiz

  1. Hangisi gerçek paralellik sağlar?

      1. threading
      1. asyncio
      1. multiprocessing
      1. time.sleep
      1. input()
  1. Aşağıdaki kodda ne eksiktir?
import threading

def islem():
    print("Merhaba")

thread = threading.Thread(target=islem)
...
    1. thread.run()
    1. thread.join()
    1. thread.start()
    1. thread.call()
    1. thread.activate()
  1. Asenkron bir fonksiyon hangi anahtar kelime ile tanımlanır?
    1. async
    1. await
    1. yield
    1. lambda
    1. defasync

Kapanış ve Tartışma

  • Threading ve multiprocessing ne zaman kullanılmalı?
  • Asenkron programlamanın avantajları nedir?
  • Gerçek dünya uygulamalarından hangileri ilgini çekiyor?

Ödev

  • Bir metin dosyasındaki kelimeleri sayan ve her biri farklı thread/prosess ile işgören bir uygulama yazın.
  • Aynı uygulamayı asyncio ile yazmayı deneyin.

Ek Kaynaklar