Nesne Tabanlı Programlama 2

12 - Eş zamanlılık, 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şlemcilere sahip olan bilgisayarlarda bu yetenekler büyük önem taşır.

Bu haftaki öğrenme hedeflerimiz:

  • Thread ve process kavramlarını tanımak,
  • 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.

join() fonksiyonu olmazsa ne olur?

Program main thread’i bitmeden bitebilir. Çıktı sıralaması garanti edilemez.

Kısacası, join() metodu, başlattığınız thread’lerin görevlerini tamamladığından emin olmak ve ana programın onlardan önce bitmesini engellemek için kullanılır. Özellikle thread’lerin ürettiği sonuçlara ana thread’de ihtiyacınız varsa veya tüm işlemlerin bittiğinden emin olmak istiyorsanız join() kullanmak önemlidir.

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
import time

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

    def increment(self):
        current_value = self.counter
        time.sleep(0.00001)
        self.counter = current_value + 1

counter = Counter()

def worker():
    for _ in range(10000):
        counter.increment()
threads = []
for _ in range(10):
    thread = threading.Thread(target=worker)
    threads.append(thread)

for thread in threads:
    thread.start()

for thread in threads:
    thread.join()

expected_value = len(threads) * 10000
print(f"Son sayaç değeri: {counter.counter}")
print(f"Beklenen sayaç değeri: {expected_value}")

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

import threading
import time

class Counter:
    def __init__(self):
        self.counter = 0
        self.lock = threading.Lock()  # Lock nesnesini başlat

    def increment(self):
        with self.lock:  # Lock'u edin (context manager ile)
            # Bu blok içindeki kod, aynı anda sadece bir thread tarafından çalıştırılabilir.
            current_value = self.counter
            # Race condition'ı göstermek için eklenen uyku, lock ile korunduğu için
            # artık veri kaybına neden olmamalı, ancak işlemleri yavaşlatabilir.
            # Çözümde bu satırı kaldırabiliriz veya bırakabiliriz.
            # Genellikle gerçek bir çözümde bu tür yapay gecikmeler olmaz.
            # Şimdilik, lock'un çalıştığını göstermesi için bırakalım.
            time.sleep(0.00001)
            self.counter = current_value + 1
        # Lock bu bloktan çıkıldığında otomatik olarak serbest bırakılır.


...

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