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 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
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
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.
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.
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ı
| 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,
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.
| 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.
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:
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:
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ı
| 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:
Böylece, tek bir uygulama çağrısı ile işletim sisteminde minimum 5 farklı Thread (ve 3 farklı Process) yönetmiş oluruz.