Warning: Undefined array key "HTTP_ACCEPT_LANGUAGE" in /var/www/vhosts/bilgigunlugum.net/httpdocs/index.php on line 43
C Programlama

Unreal Engine Oyun Programlama sayfalarımız yayında...

Ana sayfa > Programlama > C Programlama > Process ve Thread

Process ve Thread

Bir uygulama bir veya daha fazla işlemden (Process) oluşur. Bir işlem, en basit ifadeyle, çalıştırılan bir programdır. Bir veya daha fazla iş parçacığı (Thread), Process bağlamında çalışır. İş parçacığı, işletim sisteminin işlemci zamanını ayırdığı temel birimdir. Bir iş parçacığı, başka bir iş parçacığı tarafından yürütülmekte olan kısımlar da dahil olmak üzere, işlem kodunun herhangi bir bölümünü çalıştırabilir.

Windows eşzamanlılık kavramları

Windows işletim sisteminde programların çalışmasını ve kaynakları yönetmesini sağlayan temel kavramlar şunlardır:

Process (İşlem)

Çalışan bir programın bir örneğidir. İşletim sistemi kaynaklarının (bellek adresi alanı, dosya tanıtıcıları, güvenlik bilgileri vb.) bağımsız bir sahibidir. Her Process en az bir Thread (ana thread) içerir.

Programlar arasında izolasyon sağlamak için kullanılır. Bir Process'teki hata genellikle diğer Process'leri etkilemez.

Process'i, çalışan bir program için işletim sisteminin ayırdığı sanal kapsayıcı olarak düşünebilirsiniz.

Ana bileşenleri

  1. Özel Adres Alanı (Private Address Space): Bir Process'in en kritik özelliğidir. Bu, Process'in erişebileceği belleğin sanal haritasıdır. Her Process'in kendi özel adresi vardır ve bu sayede bir Process'teki hata (bellek dışına yazma), genellikle diğer Process'leri etkilemez (İzolasyon).
  2. Kaynak Sahibi (Resource Ownership): Process, dosya tanıtıcıları (file handles), ağ bağlantıları (sockets), güvenlik bilgileri ve diğer işletim sistemi kaynaklarının sahibidir.
  3. Process Kontrol Bloğu (PCB - Process Control Block): İşletim sisteminin o Process hakkındaki tüm bilgileri (durumu, Process ID'si, program sayacı, kayıt defteri içeriği, bellek sınırları vb.) tuttuğu veri yapısıdır.

Child Process oluşturma

Ana Process (Parent Process), CreateProcess gibi bir Windows API fonksiyonu kullanarak yeni bir Child Process oluşturur. Child Process, Parent Process'ten tamamen bağımsızdır. Kendi özel adres alanına, kendi kaynaklarına ve kendi ana Thread'ine sahiptir.

Bu, genellikle bir uygulamanın bir alt görevi tamamen yalıtılmış bir ortamda çalıştırması gerektiğinde (örneğin, bir web tarayıcısının her sekmeyi ayrı bir Process'te çalıştırması) kullanılır.

Thread (İş Parçacığı)

Bir Process içinde yürütmenin temel birimidir. İşlemci zamanı (CPU time) planlanan şey Thread'lerdir. Bir Process içindeki tüm Thread'ler aynı bellek alanını ve kaynakları paylaşır.

Eşzamanlılık (concurrency) ve paralellik sağlamak içindir. Bir programın birden fazla işi aynı anda veya çok hızlı bir şekilde sırayla yapmasını sağlar. Programın cevap verebilirliğini (responsiveness) artırır.

Ana bileşenleri

  1. Yürütme Bağlamı (Execution Context): Bir Thread'in çalışmaya devam edebilmesi için gereken tüm bilgileri içerir:
    • Program Sayacı (Program Counter): Çalıştırılacak bir sonraki komutun adresini tutar.
    • Kayıt Defterleri (Registers): CPU'nun anlık çalışma verilerini tutar.
  2. Yığın (Stack): Her Thread'in kendi özel yığın alanı (stack) vardır. Bu, fonksiyon çağrılarını, yerel değişkenleri ve dönüş adreslerini tutar. Bu sayede, aynı anda çalışan iki Thread aynı fonksiyonu çağırabilir ve yığınları karışmaz.
  3. Thread Kontrol Bloğu (TCB - Thread Control Block): İşletim sisteminin o Thread hakkındaki bilgileri (durumu, önceliği vb.) tuttuğu veri yapısıdır.

Kaynak paylaşımı

Bir Process içindeki tüm Thread'ler, heap (serbest bellek), global değişkenler ve dosya tanıtıcıları gibi Process kaynaklarını ortak kullanır.

Bu paylaşım, Thread'ler arası iletişimi (IPC'ye göre) çok hızlı hale getirir. Ancak bu aynı zamanda, birden fazla Thread'in aynı anda ortak bir kaynağa erişmeye çalışmasından kaynaklanabilecek yarış koşulları (race conditions) ve kilitlenme (deadlock) gibi eşzamanlılık sorunlarına karşı dikkatli olunmasını gerektirir.

Job Object (İş Nesnesi)

Bir grup Process'i tek bir birim olarak yönetmeye yarayan bir nesnedir.

Kullanım Nedeni: Bir grup Process'e (örneğin, bir uygulamanın tüm bileşenleri) aynı anda kaynak limitleri (bellek, CPU süresi) uygulamak veya ortak kısıtlamalar getirmek için kullanılır.

Thread Pool (İş Parçacığı Havuzu)

Bir görev kuyruğu ve önceden oluşturulmuş bir Thread seti (havuzu) içeren bir mekanizmadır. Gelen görevler, havuzdaki uygun bir Thread tarafından işlenir.

Thread oluşturma ve yok etme maliyetini (overhead) azaltmak ve sistemin Thread sayısını kontrol altında tutarak kaynak kullanımını optimize etmek için kullanılır. Sık sık kısa süreli görevlerin çalıştırıldığı uygulamalarda idealdir.

Fiber (Lif)

Thread'lerden daha hafif olan ve kullanıcı modunda (user-mode) programatik olarak planlanan bir yürütme birimidir. Thread'lerin aksine, Fiber'ler işletim sistemi çekirdeği (kernel) tarafından değil, programcı tarafından yönetilir.

Gelişmiş, kullanıcı modunda özelleştirilmiş zamanlama (scheduling) algoritmaları gerektiren özel durumlar için kullanılır. Nadiren kullanılır.

User-Mode Scheduling (UMS)

Uygulamaların kendi Thread'lerini (UMS Worker Thread'ler) çekirdek (kernel) yerine kullanıcı modunda yönetmelerine olanak tanıyan bir mekanizmadır.

Özellikle çok yüksek sayıda eşzamanlı Thread gerektiren uygulamalarda, çekirdek zamanlaması yerine kullanıcı modu zamanlamasını kullanarak bağlam değiştirme (context switching) maliyetini azaltmak ve performansı artırmak için kullanılır.

Neden Process veya Thread oluşturulur?

Bir C programı derlenerek elde edilen .exe dosyasını doğrudan çalıştırmak yerine Process ve Thread oluşturulmasının nedeni, bu kavramların program yürütmenin tanımı ve yönetim biçimi olmasıdır.

Bir .exe dosyası, diskteki pasif bir veri yığınıdır (kod ve veri içerir). Bu dosya, işletim sistemi tarafından belleğe yüklendiğinde ve kendisine bir kaynak alanı (Process) tahsis edildiğinde aktif hale gelir.

İşletim sistemi, bir programın çalışmasını (Process) ve çalışmanın akışını (Thread) yönetmek, izole etmek, zamanlamak ve kaynak tahsis etmek için bu soyutlamalara ihtiyaç duyar.

Bir .exe dosyasını çift tıklamak (veya komut satırında çalıştırmak), işletim sistemine "Bu dosyayı yükle ve yürütmeye başla" talimatını verir ve işletim sistemi bu talimatı yerine getirmek için otomatik olarak yeni bir Process (ve bu Process içinde en az bir Thread) oluşturur.

Child Process oluşturma

Bu bölümde, C Programlama Dili ve Windows API kullanarak bir Child Process oluşturma ve Parent ile Child Process'in çalışma sırasını gösteren örnekleri incelemeye çalışacağız.

Child Process'in tamamlanmasını bekleme

Bu örnekte, Parent Process (main fonksiyonu) basit bir mesaj yazdıracak ve ardından Child Process olarak başka bir Windows uygulaması (notepad.exe) başlatacaktır. Parent Process, Child Process'in tamamlanmasını bekleyecektir.


#include <stdio.h>
#include <windows.h>
#include <tchar.h>

// Child Process olarak başlatılacak uygulamanın adı
#define CHILD_PROCESS_APP _T("C:\\Windows\\System32\\notepad.exe")

// Parent Process'in ana fonksiyonu
int _tmain(int argc, TCHAR* argv[])
{
    // Process başlangıç bilgileri için yapı
    STARTUPINFO si;
    // Yeni Process ve onun ana Thread'i hakkındaki bilgileri tutar
    PROCESS_INFORMATION pi;

    // si yapısını sıfırla
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    // pi yapısını sıfırla
    ZeroMemory(&pi, sizeof(pi));

    // 1. ADIM: Parent Çalışıyor
    _tprintf(_T("PARENT: Ana Process basladi (ID: %lu).\n"), GetCurrentProcessId());

    // 2. ADIM: Child Process olusturuluyor
    _tprintf(_T("PARENT: Child Process olusturuluyor (%s).\n"), CHILD_PROCESS_APP);

    // CreateProcess fonksiyonu ile yeni bir Process olustur
    if (!CreateProcess(
        NULL,                       // Uygulama Modul Adi (NULL, komut satiri kullanilacaksa)
        CHILD_PROCESS_APP,          // Komut Satiri (Bu ornekte notepad.exe)
        NULL,                       // Process Handle'i guvenlik nitelikleri
        NULL,                       // Thread Handle'i guvenlik nitelikleri
        FALSE,                      // Handle miras alimi (inherit handles)
        0,                          // Olusturma bayraklari (normal olusturma)
        NULL,                       // Yeni ortam bloklari
        NULL,                       // Yeni dizin adi
        &si,                        // STARTUPINFO yapisi
        &pi                         // PROCESS_INFORMATION yapisi (sonuclar buraya yazilir)
    ))
    {
        _tprintf(_T("PARENT: CreateProcess basarisiz oldu (%lu).\n"), GetLastError());
        return 1;
    }

    // 3. ADIM: Parent bekliyor
    _tprintf(_T("PARENT: Child Process (ID: %lu) basladi. Tamamlanmasini bekliyor...\n"), pi.dwProcessId);

    // Child Process'in tamamlanmasini bekle
    // pi.hProcess, Child Process'in handle'idir.
    // INFINITE, bekleme isleminin sinirsiz olacagi anlamina gelir.
    WaitForSingleObject(pi.hProcess, INFINITE);

    // 4. ADIM: Child Process Tamamlandi
    _tprintf(_T("PARENT: Child Process tamamlandi.\n"));

    DWORD exitCode = 0;
    if (GetExitCodeProcess(pi.hProcess, &exitCode)) {
        _tprintf(_T("PARENT: Child Process cikis kodu: %lu.\n"), exitCode);
    }

    // Process ve Thread handle'larini kapat
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);

    // 5. ADIM: Parent sonlaniyor
    _tprintf(_T("PARENT: Ana Process sonlaniyor.\n"));

    return 0;
}

Yukarıdaki programı derleyip çalıştırdığımızda, aşağıdaki ifadeleri ekrana yazar:

PARENT: Ana Process basladi (ID: 18868).
PARENT: Child Process olusturuluyor (C:\Windows\System32\notepad.exe).
PARENT: Child Process (ID: 26332) basladi. Tamamlanmasini bekliyor...

Notepad uygulamasını kapattığımızda program aşağıdaki ifadeleri ekrana yazar ve sona erer:

PARENT: Child Process tamamlandi.
PARENT: Child Process cikis kodu: 0.
PARENT: Ana Process sonlaniyor.

Çalışma mantığı ve sırası

Çalışma mantığı ve sırası
Adım Process İşlem Windows API fonksiyonu Sonuç ve açıklama
1 Parent Başlangıç ve hazırlık _tmain, ZeroMemory Parent Process başlar ve STARTUPINFO ile PROCESS_INFORMATION yapılarını hazırlar.
2 Parent Child Process'i oluşturur. CreateProcess İşletim sistemine yeni bir Process (notepad.exe) oluşturması talimatını verir. Child Process burada hemen başlar.
3 Parent Child Process'i bekler. WaitForSingleObject Parent Process, engellenmiş (blocked) duruma geçer ve Child Process (Notepad) çalışırken hiçbir şey yapmaz.
4 Child Çalışma - Notepad uygulaması kullanıcı tarafından kapatılana kadar çalışır.
5 Parent Child tamamlandıktan sonra devam eder. WaitForSingleObject (dönüş) Notepad kapatıldığında bekleme sonlanır, Parent Process'in yürütmesi devam eder.
6 Parent Kaynakları serbest bırakır ve sonlanır. CloseHandle
GetExitCodeProcess
Parent Process, Child Process'in Handle'larını kapatır ve kendisi sonlanır.

CreateProcess çağrıldığında,

  1. İşletim sistemi, notepad.exe için yeni ve izole bir Process alanı oluşturur.
  2. Bu yeni Process'in kodunu çalıştırmak için otomatik olarak bir ana Thread oluşturur ve yürütmeyi bu Thread'e verir.
  3. PROCESS_INFORMATION yapısı (pi), hem yeni Process'in (pi.hProcess) hem de bu ana Thread'in (pi.hThread) handle'larını (tanıtıcılarını) içerir.

Bu örnekte, notepad.exe çalışmaya başladığında, işletim sisteminde iki ayrı Process (sizin uygulamanız ve Notepad) ve her Process içinde en az birer Thread çalışıyor olacaktır.

Yukarıdaki örnekte Child Process'i oluşturup hemen ardından WaitForSingleObject ile beklemiştik. Aşağıdaki yeni örnekte, Parent Process'in Child Process'i oluşturduktan hemen sonra onu beklemeden kendi işine devam etmesini sağlayan bir senaryo (_P_NOWAIT mantığı) uygulayacağız.

Bu durum, özellikle bir uygulamanın arka planda bir görevi başlatıp hemen kullanıcının girdilerine yanıt vermeye devam etmesi gerektiği senaryolarda önemlidir.

Child Process'in tamamlanmasını beklememe

Bu örnekte, Parent Process, Child Process'i başlatacak ve ardından hemen son mesajını yazdırıp sonlanacaktır. Child Process ise Parent'tan bağımsız olarak çalışmaya devam edecektir.


#include <stdio.h>
#include <windows.h>
#include <tchar.h>

// Child Process olarak başlatılacak uygulamanın adı ve komut satırı
// Not: Bu örnekte, Child Process'in hemen kapanmaması için 'cmd.exe' başlatıp /k parametresi ile açık tutuyoruz.
// /k parametresi, komutu çalıştırdıktan sonra pencereyi kapatmamasını söyler.
#define CHILD_COMMAND_LINE _T("cmd.exe /k echo Child Process calisiyor...")

// Parent Process'in ana fonksiyonu
int _tmain(int argc, TCHAR* argv[])
{
    STARTUPINFO si;
    PROCESS_INFORMATION pi;

    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    ZeroMemory(&pi, sizeof(pi));

    // 1. ADIM: Parent Çalışıyor
    _tprintf(_T("PARENT: Ana Process basladi (ID: %lu).\n"), GetCurrentProcessId());

    // 2. ADIM: Child Process olusturuluyor
    _tprintf(_T("PARENT: Child Process olusturuluyor (Beklenmeyecek).\n"));

    // CreateProcess fonksiyonu ile yeni bir Process olustur
    if (!CreateProcess(
        NULL,                               // Uygulama Modul Adi (NULL, komut satiri kullanilacaksa)
        CHILD_COMMAND_LINE,                 // Komut Satiri (Bu ornekte cmd.exe /k ...)
        NULL,                               // Process Handle'i guvenlik nitelikleri
        NULL,                               // Thread Handle'i guvenlik nitelikleri
        FALSE,                              // Handle miras alimi
        CREATE_NEW_CONSOLE,                 // Olusturma bayraklari: Yeni bir konsol penceresinde baslat
        NULL,                               // Yeni ortam bloklari
        NULL,                               // Yeni dizin adi
        &si,                                // STARTUPINFO yapisi
        &pi                                 // PROCESS_INFORMATION yapisi (sonuclar buraya yazilir)
    ))
    {
        _tprintf(_T("PARENT: CreateProcess basarisiz oldu (%lu).\n"), GetLastError());
        return 1;
    }

    // 3. ADIM: Parent Kendi Isine Devam Ediyor
    _tprintf(_T("PARENT: Child Process (ID: %lu) baslatildi ve serbest birakildi.\n"), pi.dwProcessId);

    // Parent, Child'i beklemek yerine kendi isine devam ediyor.
    // Ilk ornekteki 'WaitForSingleObject' cagrisi burada YOK.

    _tprintf(_T("PARENT: Kendi islerimi yapiyorum... (5 saniye bekleyecegim).\n"));
    Sleep(5000); // Parent Process 5 saniye boyunca bekler (bu sırada Child Process çalışmaya devam eder).

    // 4. ADIM: Handle'lari kapatma
    // Artık beklemeye gerek kalmadığı için handle'ları hemen kapatabiliriz.
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);

    // 5. ADIM: Parent sonlaniyor
    _tprintf(_T("PARENT: Ana Process sonlaniyor. Child Process bagimsiz calismaya devam edecek.\n"));

    return 0;
}

Yukarıdaki programı derleyip çalıştırdığımızda, aşağıdaki ifadeleri ekrana yazar:

PARENT: Ana Process basladi (ID: 21088).
PARENT: Child Process olusturuluyor (Beklenmeyecek).
PARENT: Child Process (ID: 20828) baslatildi ve serbest birakildi.
PARENT: Kendi islerimi yapiyorum... (5 saniye bekleyecegim).

5 saniye sonra program aşağıdaki ifadeleri ekrana yazar ve sona erer:

PARENT: Ana Process sonlaniyor. Child Process bagimsiz calismaya devam edecek.

Çalışma mantığı ve sırası

Bu örnekte kritik olan fark, Child Process'in oluşturulmasından sonra WaitForSingleObject fonksiyonunun kullanılmamasıdır.

Çalışma mantığı ve sırası
Adım Önceki Örnek (Bloklama) Bu Örnek (Beklemesiz)
Process başlatma CreateProcess ile Child başlatılır. CreateProcess ile Child başlatılır.
Bekleme durumu Parent Process hemen WaitForSingleObject çağrısı ile engellenir (bloke olur) ve Child bitene kadar durur. Parent Process, WaitForSingleObject çağrısını yapmaz.
Yürütme skışı Parent ve Child sıralı çalışmış gibi görünür (Child biter, sonra Parent devam eder). Parent ve Child eşzamanlı çalışır. Parent kendi işini yapmaya devam ederken, Child da arka planda kendi işini yapar.
Sonuç Child (Notepad) kapatılana kadar Parent'ın sonlanması engellenir.Parent, 5 saniye sonra sonlanır. Child (cmd.exe) ise Parent sonlandıktan sonra bile bağımsız bir Process olarak çalışmaya devam eder.

Bu yöntem, hizmetler (services), arka plan güncelleyicileri veya kullanıcı arayüzü uygulamasının uzun süren bir işlemi başka bir Process'e delege etmesi gereken durumlar için idealdir.

Thread oluşturma

Birden fazla Thread kullanımı

Bu örnekte, Ana Thread uygulamanın temel akışını yönetecek ve İki ayrı İşçi Thread (Worker Thread) ise eş zamanlı olarak farklı, uzun süren görevleri (bu örnekte basit bir döngü) yerine getirecektir. Ana Thread, tüm işçi Thread'lerin bitmesini bekleyecektir.


#include <stdio.h>
#include <windows.h>
#include <tchar.h>

// İşçi Thread'lere aktarılacak yapı (Parametreler)
typedef struct ThreadData {
    int threadId;
    int loopCount;
} ThreadData;

// 1. İŞÇİ THREAD FONKSİYONU
// Bu fonksiyon, yeni bir Thread baslatildiginda yurutecegi kodu icerir.
DWORD WINAPI WorkerThreadFunction(LPVOID lpParam)
{
    // Parametre olarak gonderilen ThreadData yapisini al
    ThreadData* data = (ThreadData*)lpParam;

    _tprintf(_T("THREAD-%d: Basladi. %d kez donme yapacak.\n"), data->threadId, data->loopCount);

    // Uzun suren bir gorevi simule eden dongu
    for (int i = 0; i < data->loopCount; i++)
    {
        // Her 1000 iterasyonda bir ilerleme mesaji goster
        if ((i + 1) % 1000 == 0)
        {
            _tprintf(_T("THREAD-%d: Ilerleme: %d/%d\n"), data->threadId, i + 1, data->loopCount);
        }

        // Cok kisa bir duraklama ile CPU'nun diger Thread'lere de zaman vermesini saglayabiliriz (Istege Bagli)
        // Sleep(0);
    }

    _tprintf(_T("THREAD-%d: Tamamlandi.\n"), data->threadId);

    // Thread'in basarili bir sekilde sonlandigini belirten cikis kodu
    return 0;
}


int _tmain(int argc, TCHAR* argv[])
{
    const int NUM_THREADS = 2;
    HANDLE hThreadArray[NUM_THREADS];
    ThreadData threadData[NUM_THREADS];

    _tprintf(_T("ANA THREAD: Uygulama basladi (ID: %lu).\n"), GetCurrentThreadId());

    // --- 1. ve 2. Isçi Thread'lerin Olusturulmasi ---

    // Thread 1 icin verileri hazirla
    threadData[0].threadId = 1;
    threadData[0].loopCount = 500000;

    // Thread 2 icin verileri hazirla
    threadData[1].threadId = 2;
    threadData[1].loopCount = 750000; // Daha uzun surecek bir is

    for (int i = 0; i < NUM_THREADS; i++)
    {
        // CreateThread fonksiyonu ile yeni bir Thread olustur
        hThreadArray[i] = CreateThread(
            NULL,                            // Guvenlik nitelikleri
            0,                               // Baslangic stack boyutu (0 = varsayilan)
            WorkerThreadFunction,            // Thread fonksiyonu (yurutulecek kod)
            &threadData[i],                  // Thread fonksiyonuna gonderilecek parametre
            0,                               // Olusturma bayraklari (0 = hemen calistir)
            NULL                             // Thread ID'si (NULL = ihtiyacimiz yok)
        );

        if (hThreadArray[i] == NULL)
        {
            _tprintf(_T("ANA THREAD: Thread olusturma basarisiz oldu. Hata kodu: %lu\n"), GetLastError());
            return 1;
        }
        _tprintf(_T("ANA THREAD: Thread-%d baslatildi.\n"), threadData[i].threadId);
    }

    _tprintf(_T("ANA THREAD: Tum Isçi Thread'ler baslatildi. Tamamlanmalarini bekliyorum...\n"));

    // --- Isçi Thread'lerin Beklenmesi ---

    // Tum isçi Thread'lerin tamamlanmasini bekle.
    // WaitForMultipleObjects: Birden fazla nesneyi (Thread handle'ini) bekler.
    WaitForMultipleObjects(
        NUM_THREADS,         // Beklenecek nesne sayisi
        hThreadArray,        // Nesne handle'larinin dizisi
        TRUE,                // TRUE: Tum nesnelerin sinyal vermesini bekle (AND operasyonu)
        INFINITE             // Sinirsiz sure bekle
    );

    // --- Sonlandirma ---

    _tprintf(_T("ANA THREAD: Tum Isçi Thread'ler tamamlandi.\n"));

    // Handle'lari kapat
    for (int i = 0; i < NUM_THREADS; i++)
    {
        CloseHandle(hThreadArray[i]);
    }

    _tprintf(_T("ANA THREAD: Uygulama basarili sekilde sonlaniyor.\n"));

    return 0;
}

Yukarıdaki programı derleyip çalıştırdığımızda, aşağıdaki ifadeleri ekrana yazar:

ANA THREAD: Uygulama basladi (ID: 30572).
ANA THREAD: Thread-1 baslatildi.
THREAD-1: Basladi. 500000 kez donme yapacak.
THREAD-1: Ilerleme: 1000/500000
THREAD-1: Ilerleme: 2000/500000
THREAD-1: Ilerleme: 3000/500000
THREAD-1: Ilerleme: 4000/500000
THREAD-1: Ilerleme: 5000/500000
ANA THREAD: Thread-2 baslatildi.
ANA THREAD: Tum Isci Thread'ler baslatildi. Tamamlanmalarini bekliyorum...
THREAD-2: Basladi. 750000 kez donme yapacak.
THREAD-2: Ilerleme: 1000/750000
THREAD-2: Ilerleme: 2000/750000
THREAD-2: Ilerleme: 3000/750000
.
.
.
THREAD-2: Ilerleme: 747000/750000
THREAD-2: Ilerleme: 748000/750000
THREAD-2: Ilerleme: 749000/750000
THREAD-2: Ilerleme: 750000/750000
THREAD-2: Tamamlandi.
ANA THREAD: Tum Is├ği Thread'ler tamamlandi.
ANA THREAD: Uygulama basarili sekilde sonlaniyor.

Çalışma mantığı ve sırası

Bu kodun çalışması, Multi-threading'in temel faydalarını gösterir:

  1. Ana Thread (Main Thread): Uygulamanın kontrolünü başlatır ve CreateThread fonksiyonunu iki kez çağırarak iki yeni yürütme akışı (Worker Thread 1 ve 2) oluşturur.
  2. Eşzamanlı yürütme: Worker Thread 1 ve Worker Thread 2 aynı anda (çok çekirdekli sistemde tam paralel, tek çekirdekte zaman dilimleriyle dönüşümlü) çalışmaya başlar. Her iki Thread de kendi WorkerThreadFunction fonksiyonunu çalıştırır, ancak parametre olarak farklı döngü sayıları almıştır.
  3. Hızlı yanıt verme (Responsiveness): Bu senaryoda, Ana Thread'in görevi sadece yönetimdir. Ana Thread, uzun sürecek olan hesaplama görevlerini İşçi Thread'lere devrederek kendisi (eğer bir kullanıcı arayüzü olsaydı) kullanıcı girdilerine yanıt vermeye devam edebilirdi.
  4. Bloklama ve senkronizasyon: Ana Thread, tüm işçi Thread'lerin bitmesini garanti etmek için WaitForMultipleObjects fonksiyonunu kullanır. Bu çağrı, tüm işçi Thread'ler işlerini bitirene kadar Ana Thread'i bloke eder. Bu, Thread'ler arası basit bir senkronizasyon mekanizmasıdır.
  5. Kaynak paylaşımı: Her iki işçi Thread de aynı Process'in bellek alanını, global değişkenlerini ve diğer kaynaklarını paylaşır (bu örnekte _tprintf ile konsol çıktısını paylaşırlar, bu da çıktıların karışık görünmesine neden olabilir; bu da eşzamanlılıkta yarış koşulu riskine bir örnektir).

Process ve Thread'leri birlikte oluşturma

Bu bölümde, bir uygulama ile birlikte otomatik olarak oluşturulan Parent Process ve Parent Thread'e ilave olarak 2 adet child Process oluşturup, Parent Process ve bir adet child Process için birer adet ek Thread oluşturan ve çalıştıran bir örnek incelemeye çalışacağız.

Program kodları aşağıdaki işlemleri yapacak:

  1. Ana Program (Parent Process): Kendi ana main Thread'i ile başlar.
  2. Parent Ek Thread: Parent Process içinde ek bir Thread (ParentWorkerThread) oluşturur.
  3. Child Process 1: Başka bir .exe dosyasını Child Process olarak başlatır.
  4. Child Process 1 Ek Thread: Child Process 1'i başlattıktan sonra, bu Child Process'in içinde ek bir Thread (ChildWorkerThreadFunction) çalıştıracak şekilde tasarlanmıştır. (Not: Bunu gerçekleştirmek için Child Process'in de kendi içinde bir Thread oluşturma koduna ihtiyacı vardır. Biz bu senaryoda kolaylık olması açısından Child Process'in basit bir uygulama olduğunu varsayacağız ve Child Process'in kendi ek Thread'ini Child Process'in kendisinin oluşturması gerekecektir.)
  5. Child Process 2: Farklı bir .exe dosyasını ikinci Child Process olarak başlatır.

Kısıtlama Notu: Bir Parent Process, bir Child Process'in içinde Thread oluşturamaz. En doğru yaklaşım, Child Process'in kendi kodu içinde ek Thread'ler oluşturmasıdır.

Basitleştirmek adına, Child Process'ler için Windows'ta bulunan basit komut satırı araçlarını (notepad.exe ve cmd.exe) kullanacağız ve ek Thread oluşturma işini Parent Process'te yoğunlaştıracağız.

Bu projeyi gerçekleştirmek için, bir ana uygulama (ParentApp.exe) diğeri yan uygulama (ChildApp.exe) olmak üzere 2 .exe uzantılı dosya oluşturacağız.

1. Önce ParentApp.exe dosyasını oluşturmak için aşağıdaki kaynak kodu derleyelim:


#include <stdio.h>
#include <windows.h>
#include <tchar.h>

// Child Process'in dosya yolu (Adim 2'de derlenecek dosya)
#define CHILD_APP_PATH _T(".\\ChildApp.exe") 
// Ikinci Child Process
#define CHILD_APP_2 _T("notepad.exe")

// --- Parent Process Icindeki Ek Thread Fonksiyonu ---
DWORD WINAPI ParentWorkerThread(LPVOID lpParam) 
{
    int delaySeconds = *((int*)lpParam);
    _tprintf(_T("  [PARENT EK THREAD]: Basladi (ID: %lu). %d saniye calisacak.\n"), GetCurrentThreadId(), delaySeconds);
    
    for (int i = 0; i < delaySeconds; i++)
    {
        Sleep(1000); 
        _tprintf(_T("  [PARENT EK THREAD]: Gecen sure %d saniye.\n"), i + 1);
    }

    _tprintf(_T("  [PARENT EK THREAD]: Gorev tamamlandi ve sonlaniyor.\n"));
    return 0;
}


// --- ANA FONKSIYON (Parent Process ve Ana Thread) ---
int _tmain(int argc, TCHAR* argv[]) 
{
    // Parent Process'e ait ek Thread icin handle ve data
    HANDLE hParentWorkerThread;
    int parentThreadDelay = 5;

    // Child Process'ler icin yapilar
    STARTUPINFO si[2];
    PROCESS_INFORMATION pi[2];
    
    // Child Process komut satirlari
    TCHAR child1Cmd[] = CHILD_APP_PATH; // ChildApp.exe'yi cagir
    TCHAR child2Cmd[] = CHILD_APP_2;    // Notepad'i cagir
    TCHAR* childCmds[] = { child1Cmd, child2Cmd };
    
    for(int i = 0; i < 2; i++) {
        ZeroMemory(&si[i], sizeof(si[i]));
        si[i].cb = sizeof(si[i]);
        ZeroMemory(&pi[i], sizeof(pi[i]));
    }

    _tprintf(_T("=================================================================\n"));
    _tprintf(_T("PARENT ANA THREAD: Uygulama basladi (ID: %lu).\n"), GetCurrentThreadId());
    _tprintf(_T("-----------------------------------------------------------------\n"));


    // 1. Adim: Parent Process Icine Ek Thread Olusturma
    hParentWorkerThread = CreateThread(
        NULL, 0, ParentWorkerThread, &parentThreadDelay, 0, NULL
    );

    if (hParentWorkerThread == NULL) {
        _tprintf(_T("HATA: Parent Ek Thread olusturulamadi.\n")); return 1;
    }
    _tprintf(_T("PARENT ANA THREAD: Parent Ek Thread baslatildi.\n"));


    // 2. Adim: 2 Adet Child Process Olusturma
    for (int i = 0; i < 2; i++)
    {
        _tprintf(_T("PARENT ANA THREAD: Child Process %d olusturuluyor: %s\n"), i + 1, childCmds[i]);
        
        if (!CreateProcess(
            NULL, childCmds[i], NULL, NULL, FALSE, 0, NULL, NULL, &si[i], &pi[i]
        )) 
        {
            _tprintf(_T("HATA: Child Process %d olusturulamadi (%lu).\n"), i + 1, GetLastError());
            pi[i].hProcess = NULL; pi[i].hThread = NULL;
            continue;
        }

        _tprintf(_T("PARENT ANA THREAD: Child Process %d (ID: %lu) baslatildi.\n"), i + 1, pi[i].dwProcessId);
        CloseHandle(pi[i].hThread); // Child'in ana Thread handle'ini kapat
    }

    _tprintf(_T("-----------------------------------------------------------------\n"));
    _tprintf(_T("PARENT ANA THREAD: Parent Ek Thread'in bitmesini bekliyorum...\n"));
    
    // Parent Ana Thread, kendi ek Thread'inin bitmesini bekler
    WaitForSingleObject(hParentWorkerThread, INFINITE);

    _tprintf(_T("PARENT ANA THREAD: Parent Ek Thread bitti.\n"));
    
    // Tum Child Process'lerin bitmesini bekle
    // ChildApp.exe 7 saniye sonra bitecek, notepad.exe kullanici kapatana kadar calisacak.
    _tprintf(_T("PARENT ANA THREAD: Tum Child Process'lerin bitmesini bekliyorum...\n"));
    
    HANDLE hChildProcesses[] = { pi[0].hProcess, pi[1].hProcess };
    WaitForMultipleObjects(2, hChildProcesses, TRUE, INFINITE);

    _tprintf(_T("-----------------------------------------------------------------\n"));
    _tprintf(_T("PARENT ANA THREAD: Tum Child Process'ler bitti. Uygulama sonlaniyor.\n"));

    CloseHandle(hParentWorkerThread);
    CloseHandle(pi[0].hProcess); 
    CloseHandle(pi[1].hProcess); 

    return 0;
}

2. ChildApp.exe dosyasını oluşturmak için aşağıdaki kaynak kodu derleyelim:


#include <stdio.h>
#include <windows.h>
#include <tchar.h>

// Child Process'in Ek Thread Fonksiyonu
DWORD WINAPI ChildWorkerThread(LPVOID lpParam) 
{
    int threadId = *((int*)lpParam);
    
    _tprintf(_T("  [CHILD EK THREAD]: Basladi (ID: %lu). 7 saniye calisacak.\n"), GetCurrentThreadId());
    
    // Uzun sureli bir isi simule et
    for (int i = 0; i < 7; i++)
    {
        Sleep(1000); // 1 saniye bekle
        _tprintf(_T("  [CHILD EK THREAD]: Gecen sure %d saniye.\n"), i + 1);
    }

    _tprintf(_T("  [CHILD EK THREAD]: Gorev tamamlandi ve sonlaniyor.\n"));
    return 0;
}

// Child Process'in Ana Fonksiyonu (Ana Thread)
int _tmain(int argc, TCHAR* argv[]) 
{
    HANDLE hWorkerThread;
    int workerThreadId = 99; // Ornek bir ID
    
    _tprintf(_T("CHILD PROCESS: Basladi. Ana Process ID: %lu, Ana Thread ID: %lu\n"), GetCurrentProcessId(), GetCurrentThreadId());
    
    // Ek Thread'i Olustur
    hWorkerThread = CreateThread(
        NULL,                            // Guvenlik nitelikleri
        0,                               // Stack boyutu
        ChildWorkerThread,               // Thread fonksiyonu
        &workerThreadId,                 // Parametre (ID)
        0,                               // Olusturma bayraklari
        NULL                             // Thread ID
    );

    if (hWorkerThread == NULL) {
        _tprintf(_T("CHILD PROCESS: HATA! Ek Thread olusturulamadi (%lu).\n"), GetLastError());
        return 1;
    }
    _tprintf(_T("CHILD PROCESS: Ek Thread baslatildi. Kendi Ek Thread'inin bitmesini bekliyor.\n"));
    
    // Ana Thread, kendi olusturdugu Ek Thread'in bitmesini bekler
    WaitForSingleObject(hWorkerThread, INFINITE);

    CloseHandle(hWorkerThread);
    
    _tprintf(_T("CHILD PROCESS: Tum Thread'ler bitti. Child Process sonlaniyor.\n"));
    
    // Child Process sonlaniyor.
    return 0;
}

Yukarıdaki programı derleyip çalıştırdığımızda, aşağıdaki ifadeleri ekrana yazar:

=================================================================
PARENT ANA THREAD: Uygulama basladi (ID: 23636).
-----------------------------------------------------------------
PARENT ANA THREAD: Parent Ek Thread baslatildi.
PARENT ANA THREAD: Child Process 1 olusturuluyor: .\ChildApp.exe
  [PARENT EK THREAD]: Basladi (ID: 26832). 5 saniye calisacak.
PARENT ANA THREAD: Child Process 1 (ID: 24184) baslatildi.
PARENT ANA THREAD: Child Process 2 olusturuluyor: notepad.exe
CHILD PROCESS: Basladi. Ana Process ID: 24184, Ana Thread ID: 29980
CHILD PROCESS: Ek Thread baslatildi. Kendi Ek Thread'inin bitmesini bekliyor.
  [CHILD EK THREAD]: Basladi (ID: 31184). 7 saniye calisacak.
PARENT ANA THREAD: Child Process 2 (ID: 31096) baslatildi.
-----------------------------------------------------------------
PARENT ANA THREAD: Parent Ek Thread'in bitmesini bekliyorum...
  [PARENT EK THREAD]: Gecen sure 1 saniye.
  [CHILD EK THREAD]: Gecen sure 1 saniye.
  [PARENT EK THREAD]: Gecen sure 2 saniye.
  [CHILD EK THREAD]: Gecen sure 2 saniye.
  [PARENT EK THREAD]: Gecen sure 3 saniye.
  [CHILD EK THREAD]: Gecen sure 3 saniye.
  [PARENT EK THREAD]: Gecen sure 4 saniye.
  [CHILD EK THREAD]: Gecen sure 4 saniye.
  [PARENT EK THREAD]: Gecen sure 5 saniye.
  [PARENT EK THREAD]: Gorev tamamlandi ve sonlaniyor.
PARENT ANA THREAD: Parent Ek Thread bitti.
PARENT ANA THREAD: Tum Child Process'lerin bitmesini bekliyorum...
  [CHILD EK THREAD]: Gecen sure 5 saniye.
  [CHILD EK THREAD]: Gecen sure 6 saniye.
  [CHILD EK THREAD]: Gecen sure 7 saniye.
  [CHILD EK THREAD]: Gorev tamamlandi ve sonlaniyor.
CHILD PROCESS: Tum Thread'ler bitti. Child Process sonlaniyor.

Notepad uygulamasını kapattığımızda program aşağıdaki ifadeleri ekrana yazar ve sona erer:

-----------------------------------------------------------------
PARENT ANA THREAD: Tum Child Process'ler bitti. Uygulama sonlaniyor.

Çalışma mantığı ve sırası

Çalışma mantığı ve sırası
Adım Yürütücü birim İşlem Sonuç
Başlangıç Parent Process / Ana Thread Uygulama başlatılır. Parent'ın ana Thread'i yürütülmeye başlar.
1 Parent Process / Ana Thread Kendi Ek Thread'ini oluşturur. Parent Ek Thread (ParentWorkerThread) oluşturulur ve hemen çalışmaya başlar (5 saniye süren iş).
2 Parent Process / Ana Thread Child Process 1'i oluşturur. Child Process 1 (ChildApp.exe) başlatılır. Child 1'in ana Thread'i çalışmaya başlar ve hemen kendi Ek Thread'ini oluşturur.
3 Child Process 1 / Ana Thread Kendi Ek Thread'ini bekler. Child 1'in ana Thread'i, Child Ek Thread'inin (7 saniyelik iş) bitmesi için WaitForSingleObject çağrısı yapar ve engellenir.
4 Parent Process / Ana Thread Child Process 2'yi oluşturur. Child Process 2 (notepad.exe) başlatılır ve kendi ana Thread'i ile çalışır.
Eşzamanlı yürütme Parent Ek Thread 5 saniyelik görevini sürdürür. Parent Ana Thread'den bağımsız çalışır.
  Child Ek Thread 7 saniyelik görevini sürdürür. Child 1 Ana Thread'den ve Parent'tan bağımsız çalışır.
  Child Process 2 Kullanıcı girdisini bekler. Kullanıcı kapatana kadar çalışır.
5 Parent Process / Ana Thread Parent Ek Thread'i bekler. WaitForSingleObject ile Parent Ek Thread'inin (5 saniye) bitmesini bekler.
6 Child Process 1 (Tümü) Child Ek Thread'i sonlanır. 7 saniye dolduğunda Child Ek Thread biter. Bunun üzerine engellenen Child 1 Ana Thread serbest kalır, kaynakları kapatır ve Child Process 1 sonlanır.
7 Parent Process / Ana Thread Child Process'leri bekler. Parent Ek Thread bittikten sonra, Child Process 1 (zaten bitti) ve Child Process 2'nin (notepad.exe) bitmesini bekler.
Son Parent Process / Ana Thread Sonlandırma Kullanıcı notepad.exe'yi kapattığında bekleme sonlanır, Parent Process handle'ları kapatır ve uygulama sonlanır.

Bu kodda eş zamanlı çalışan birimler şunlardır:

  • Parent Process içinde:
    1. Ana Thread
    2. Parent Ek Thread
  • Ayrı Process'ler olarak:
    1. Child Process 1 (Ana Thread'i ile)
    2. Child Process 1 (Ek Thread'i ile)
    3. Child Process 2 (Ana Thread'i ile)

Böylece, tek bir uygulama çağrısı ile işletim sisteminde minimum 5 farklı Thread (ve 3 farklı Process) yönetmiş oluruz.