Programozás | Egyéb » Többszálú programozás Windows NT 4 alatt

Alapadatok

Év, oldalszám:1999, 38 oldal

Nyelv:magyar

Letöltések száma:1046

Feltöltve:2015. január 23.

Méret:329 KB

Intézmény:
[BME] Budapesti Műszaki és Gazdaságtudományi Egyetem

Megjegyzés:

Csatolmány:-

Letöltés PDF-ben:Kérlek jelentkezz be!



Értékelések

Nincs még értékelés. Legyél Te az első!

Tartalmi kivonat

Budapesti Műszaki Egyetem Automatizálási és Alkalmazott Informatikai Tanszék TÖBBSZÁLÚ PROGRAMOZÁS WINDOWS NT 4 ALATT 1999. június Budapest Eredeti cím: Herbert Schildt: Windows NT 4 Programming from the Ground Up Chapter 15: Thread-Based Multitasking Free code on web page: http://www.osbornecom A magyar fordítást írta: Balássy György S8337bal@hszk.bmehu Többszálú programozás Windows NT alatt Tartalomjegyzék TARTALOMJEGYZÉK . 2 BEVEZETŐ. 3 TÖBBSZÁLÚ PROGRAM KÉSZÍTÉSE . 3 ÚJ SZÁL LÉTREHOZÁSA . 3 SZÁL MEGSZŰNTETÉSE . 4 EGY TÖBBSZÁLÚ PÉLDA . 4 NÉHÁNY RÉSZLET AZ ELŐZŐ PÉLDÁVAL KAPCSOLATBAN . 6 ALTERNATÍVÁK A CREATETHREAD() ÉS AZ EXITTHREAD() FÜGGVÉNYEKRE . 6 A MICROSOFT ALTERNATÍVÁK. 7 A BORLAND ALTERNATÍVÁK. 7 A MICROSOFT C/C++ SZÁLAKKAL KAPCSOLATOS FÜGGVÉNYEINEK HASZNÁLATA . 8 A STANDARD C KÖNYVTÁRI FÜGGVÉNYEK ELKERÜLÉSE . 10 SZÁL FELFÜGGESZTÉSE ÉS FELÉLESZTÉSE . 11 A SZÁLAK PRIORITÁSA . 11 A PRIORITÁS

OSZTÁLYOK . 11 A SZÁL PRIORITÁS . 12 A SZÁLVEZÉRLŐ PANEL ELKÉSZÍTÉSE . 12 A SZÁLVEZÉRLŐ PANEL PROGRAM . 13 NÉHÁNY RÉSZLET A SZÁLVEZÉRLŐ PANELLEL KAPCSOLATBAN. 19 SZINKRONIZÁLÁS . 22 A SZINKRONIZÁCIÓS PROBLÉMA LÉNYEGE . 23 A WINDOWS NT SZINKRONIZÁLÓ OBJEKTUMAI . 23 SZÁLAK SZINKRONIZÁLÁSA SZEMAFOR SEGÍTSÉGÉVEL . 24 A SZEMAFOR PROGRAM RÉSZLETEI . 28 ESEMÉNY OBJEKTUM HASZNÁLATA . 29 WAITABLE TIMER HASZNÁLATA. 30 WAITABLE TIMER HASZNÁLATA . 35 ÚJ TASZK KÉSZÍTÉSE . 36 2. oldal Többszálú programozás Windows NT alatt Bevezető bben a fejezetben a Windows NT szál alapú multitaszkolásáról (azaz feladat párhuzamosításáról) lesz szó. A Windows NT ugyanis a párhuzamosítás két módját támogatja: a folyamat- és a szál alapú multitaszkolást. A folyamat alapú multitaszkolást a Windows már a kezdetek óta támogatja A folyamat lényegében egy futó program. Folyamat alapú multitaszkolás esetén kettő vagy több folyamat

futhat egyidejűleg. A párhuzamosítás másik módja a szál alapú multitaszkolás A szál egy folyamaton belül egy végrehajtási útvonal. Windows NT alatt minden folyamatnak van legalább egy szála, de lehet kettő vagy akár több is. Szál alapú multitaszkolás esetén egy program kettő vagy több része futhat egymással párhuzamosan A multitaszkolásnak ez a módja igen hatékony programok írását teszi lehetővé, mert a programozó maga definiálhat szálakat a programján belül és így határozhatja meg a program végrehajtásának módját. E A szál alapú multitaszkolás magával hozta a szinkronizálás szükségességét is, aminek segítségével a szálak (és a folyamatok) együttműködése koordinálható bizonyos jól meghatározott módokon. A Windows NT-ben egy teljes alrendszer foglalkozik a szinkronizálással, amely újabb funkciókkal bővült a 4-es verzióban. A legfontosabb funkciókat tárgyaljuk ebben a fejezetben. Hordozhatóság: A

Windows 3.1 nem támogatja a szál alapú multitaszkolást Többszálú program készítése Ha egy programozó még eddig soha nem készített többszálú multitaszkolásra képes rendszerre programot, akkor itt most igen kellemes tapasztalatok érhetik. A multitaszkolás ugyanis új távlatokat nyit meg a programozó előtt, aki maga határozhatja meg, hogy a programjának egyes darabjai hogyan hajtódjanak végre. Ezáltal sokkal hatékonyabb programok írhatók. Például a program egyik szálja a fájlműveleteket végzi, miközben egy másik szál egy külső eszközről információkat gyűjt, egy harmadik szál pedig a felhasználói műveleteket kezeli. A többszálú multitaszkolás miatt ezek a szálak egymással párhuzamosan hajthatók végre úgy, hogy közben a processzort a lehető legjobban kihasználják. Fontos megérteni, hogy minden folyamatnak van legalább egy végrehajtási szálja, ez a fő szál. A fő szálból van lehetőség elágazni, és újabb

szálakat létrehozni ugyanazon a folyamaton belül. Általában, ha létrehozunk egy új szálat, annak a végrehajtása is azonnal megkezdődik. Éppen ezért, minden folyamat egyetlen szállal indul és létrehozhat egy vagy több új szálat. Új szál létrehozása Új szál a CreateThread() API függvény segítségével hozható létre. A függvény prototípusa a következő: HANDLE CreateThread(LPSECURITY ATTRIBUTES lpSecAttr, DWORD dwStackSize, LPTHREAD START ROUTINE lpThreadFunc, LPVOID lpParam, DWORD dwFlags, LPDWORD lpdwThreadID); Itt lpSecAttr egy olyan mutató, aminek segítségével a szálhoz tartozó biztonsági attribútumokat állíthatjuk be. Ha lpSecAttr értéke NULL, a függvény az alapértelmezett security descriptort használja. Megjegyzés: Ebben a fejezetben mindig az alapértelmezett security descriptort használjuk. Minden szálnak van saját stackje. Ennek a stacknek a mérete adható meg bájtokban a dwStackSize értékeként Ha ez nulla, akkor

szálnak akkora stackje lesz, mint amekkora az őt létrehozó szálnak van. Ebben az esetben a stack mérete szükség esetén megnövelődik. A dwStackSize értékének nullára állítása általánosan elterjedt módszer. Minden szál végrehajtása egy függvény, a szálfüggvény meghívásával kezdődik a folyamaton belül. A szál végrehajtása egészen addig folytatódik, amíg a szálfüggvény vissza nem tér. Ennek a függvénynek a címe, azaz 3. oldal Többszálú programozás Windows NT alatt tulajdonképpen a szál kezdete adható meg az lpThreadFunc paraméterrel. Minden szálfüggvény prototípusának ilyennek kell lennie: DWORD WINAPI szalfuggveny(LPVOID param); A szálfüggvény számára átadandó paraméterek a CreateThread() függvény lpParam változójában megadhatók. Ezt a 32 bites értéket a szálfüggvény a paraméterében megkapja, és a függvényen belül tetszőleges célra felhasználható. A függvény a kilépési állapotának

megfelelő értékkel tér vissza A dwFlags a szál végrehajtási módját és állapotát határozza meg. Ha ez nulla, akkor a szál végrehajtása azonnal megkezdődik. Ha CREATE SUSPEND, akkor a szál felfüggesztett állapotban jön létre (Az ilyen szálat a később tárgyalandó ResumeThread() függvény segítségével lehet elindítani.) A szál azonosítóját az lpdwThreadID változóban kapjuk vissza. A függvény sikeres hívás esetén a szálhoz tartozó leíróval tér vissza; ha hiba lép fel, a visszatérési érték NULL. Szál megszűntetése Mint mondtuk, egy szál végrehajtása megszakad, ha a szálfüggvény visszatér. Azonban a folyamat megszakíthatja szálat manuálisan is, a TerminateThread() vagy az ExitThread() függvények segítségével, melyeknek prototípusa a következő: BOOL TerminateThread(HANDLE hThread, DWORD dwStatus); VOID ExitThread(DWORD dwStatus); A TerminateThread() függvényben hThread a bezárni kívánt szál leírója. Az

ExitThread() pedig azt a szálat szakítja meg, amelyik meghívja. Mindkét függvény esetén a dwStatus paraméterben a kilépési állapot adható meg. A TerminateThread() nemnulla értékkel tér vissza sikeres hívás esetén, egyébként pedig nullával Az ExitThread() meghívása lényegében azonos a függvény normális visszatérésével, azaz a stack szabályosan lebontásra kerül. Ha egy szálat a TerminateThread() függvény szakít meg, annak a futása azonnal megszakad, és nem történik meg a stack takarítása. Ráadásul a TerminateThread() akár egy fontos művelet közben is bezárhat egy szálat. Éppen ezért általában a legjobb (és egyben a legegyszerűbb) megoldás az, ha egy szál szabályosan záródik be akkor, amikor a szálfüggvény visszatér. Ebben a fejezetben ezt a módszert használjuk a példaprogramjainkban. Egy többszálú példa Az alábbi program a Demonstrate Threads menüpont kiválasztásakor két új szálat hoz létre. Mindkét

szálban egy-egy for ciklus fut le 5000-szer, miközben kiírja a képernyőre az iteráció számát minden egyes iterációnál. A program futtatásakor látható, hogy a két szál látszólag párhuzamosan fut. /* A simple multithreaded program. */ #include <windows.h> #include <string.h> #include <stdio.h> #include "thread.h" #define MAX 5000 LRESULT CALLBACK WindowFunc(HWND, UINT, WPARAM, LPARAM); DWORD WINAPI MyThread1(LPVOID param); DWORD WINAPI MyThread2(LPVOID param); char szWinName[] = "MyWin"; /* name of window class / char str[255]; /* holds output strings / DWORD Tid1, Tid2; /* thread IDs / int WINAPI WinMain(HINSTANCE hThisInst, HINSTANCE hPrevInst, LPSTR lpszArgs, int nWinMode) { HWND hwnd; MSG msg; WNDCLASSEX wcl; HANDLE hAccel; /* Define a window class. */ wcl.cbSize = sizeof(WNDCLASSEX); wcl.hInstance = hThisInst; wcl.lpszClassName = szWinName; wcl.lpfnWndProc = WindowFunc; wcl.style = 0; /* /* /* /* handle to this instance

*/ window class name */ window function */ default style */ 4. oldal Többszálú programozás Windows NT alatt wcl.hIcon = LoadIcon(NULL, IDI APPLICATION); wcl.hIconSm = LoadIcon(NULL, IDI APPLICATION); wcl.hCursor = LoadCursor(NULL, IDC ARROW); /* specify name of menu resource / wcl.lpszMenuName = "ThreadMenu"; /* main menu / wcl.cbClsExtra = 0; wcl.cbWndExtra = 0; /* no extra / /* information needed / /* Make the window white. */ wcl.hbrBackground = GetStockObject(WHITE BRUSH); /* Register the window class. */ if(!RegisterClassEx(&wcl)) return 0; /* Now that a window class has been registered, a window can be created. */ hwnd = CreateWindow( szWinName, /* name of window class / "Demonstrate Threads", /* title / WS OVERLAPPEDWINDOW, /* window style - normal / CW USEDEFAULT, /* X coordinate - let Windows decide / CW USEDEFAULT, /* Y coordinate - let Windows decide / CW USEDEFAULT, /* width - let Windows decide / CW USEDEFAULT, /* height - let Windows

decide / HWND DESKTOP, /* no parent window / NULL, /* no override of class menu / hThisInst, /* handle of this instance of the program / NULL /* no additional arguments / ); /* load accelerators / hAccel = LoadAccelerators(hThisInst, "ThreadMenu"); /* Display the window. */ ShowWindow(hwnd, nWinMode); UpdateWindow(hwnd); /* Create the message loop. */ while(GetMessage(&msg, NULL, 0, 0)) { if(!TranslateAccelerator(hwnd, hAccel, &msg)) { TranslateMessage(&msg); /*translate keyboard messages / DispatchMessage(&msg); /*return control to Windows NT / } } return msg.wParam; } /* This function is called by Windows NT and is passed messages from the message queue. */ LRESULT CALLBACK WindowFunc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { int response; switch(message) { case WM COMMAND: switch(LOWORD(wParam)) { case IDM THREAD: /* create the threads / CreateThread(NULL, 0, (LPTHREAD START ROUTINE)MyThread1, (LPVOID) hwnd, 0, &Tid1); CreateThread(NULL,

0, (LPTHREAD START ROUTINE)MyThread2, (LPVOID) hwnd, 0, &Tid2); break; case IDM EXIT: response = MessageBox(hwnd, "Quit the Program?", "Exit", MB YESNO); if(response == IDYES) PostQuitMessage(0); break; case IDM HELP: MessageBox(hwnd, "F1: Help F2: Demonstrate Threads", "Help", MB OK); break; } break; case WM DESTROY: /* terminate the program / PostQuitMessage(0); break; default: /* Let Windows NT process any messages not specified in the preceding switch statement. */ return DefWindowProc(hwnd, message, wParam, lParam); } return 0; } /* A thread of execution within the process. */ DWORD WINAPI MyThread1(LPVOID param) { int i; HDC hdc; for(i=0; i<MAX; i++) { sprintf(str, "Thread 1: loop # %5d ", i); hdc = GetDC((HWND) param); 5. oldal Többszálú programozás Windows NT alatt TextOut(hdc, 1, 1, str, strlen(str)); ReleaseDC((HWND) param, hdc); } return 0; } /* Another thread of execution within the process. */ DWORD WINAPI

MyThread2(LPVOID param) { int i; HDC hdc; for(i=0; i<MAX; i++) { sprintf(str, "Thread 2: loop # %5d ", i); hdc = GetDC((HWND) param); TextOut(hdc, 1, 20, str, strlen(str)); ReleaseDC((HWND) param, hdc); } return 0; } Ez a program az alábbi THREAD.H header fájlt használja: #define IDM THREAD 100 #define IDM HELP 101 #define IDM EXIT 102 Továbbá szükség van a következő erőforrás fájlra is: #include <windows.h> #include "thread.h" ThreadMenu MENU { POPUP "&Threads" { MENUITEM "Demonstrate &Threads F2", IDM THREAD MENUITEM "E&xit F10", IDM EXIT } MENUITEM "&Help", IDM HELP } ThreadMenu ACCELERATORS { VK F2, IDM THREAD, VIRTKEY VK F10, IDM EXIT, VIRTKEY VK F1, IDM HELP, VIRTKEY } Néhány részlet az előző példával kapcsolatban A Demonstrate Threads menüpont kiválasztásakor az alábbi kód kerül végrehajtásra: case IDM THREAD: /* create the threads / CreateThread(NULL, 0, (LPTHREAD

START ROUTINE)MyThread1, (LPVOID) hwnd, 0, &Tid1); CreateThread(NULL, 0, (LPTHREAD START ROUTINE)MyThread2, (LPVOID) hwnd, 0, &Tid2); break; Amint látható a CreateThread() első hívása elindítja MyThread1()-t, a második pedig MyThread2()-t. Érdemes megfigyelni, hogy a fő ablak leíróját, hwnd-t, mindkét szálfüggvény megkapja paraméterként. Ezt a leírót mindkét függvény arra használja, hogy eszköz környezetet készítsen, és ezáltal információkat írhasson ki a fő ablakra. Indítás után mindegyik szál (beleértve a fő szálat is) végrehajtása egymástól függetlenül zajlik. Például miközben a szálak futnak, ki lehet választani a Help menüpontot, ki lehet lépni a programból, vagy újabb szálakat indítani. A programból való kilépéskor minden gyermekszál futása automatikusan befejeződik Mielőtt továbblépnénk, érdemes részletesebben is megismerkedni ezzel a példaprogrammal kicsit részletesebben is. Jelenlegi

állapotában a szálak futása akkor fejeződik be, amikor a szálfüggvények visszatérnek Érdemes megpróbálni az ExitThread() alkalmazásával úgy átírni a programot, hogy a szálak korábban érjenek véget vagy több példányt létrehozni mindegyik szálból. Alternatívák a CreateThread() és az ExitThread() függvényekre A használt C/C++ fordítótól és az alkalmazott standard C könyvtári függvényektől függően előfordulhat, hogy érdemes elkerülni a CreateThread() és az ExitThread() függvényeket, mert a használatuk memória szivárgást (memory leak) eredményezhet. A memória szivárgás a felhasználható memória egy kis részének elvesztését jelenti. Ez leginkább akkor fordulhat elő, ha egy program a kilépéskor nem szabadítja fel az általa lefoglalt memória területet. Sok C/C++ fordító esetén beleértve a Microsoft Visual C++ és a Borland C++ fordítót is ha egy többszálú program a standard C könyvtári függvényeket és a

CreateThread(), ExitThread() függvényeket egyaránt használja, akkor ez a memória egy részének elvesztését okozhatja. (Ilyen memória szivárgás nem lép fel, ha a program nem használ standard C könyvtári függvényeket.) A probléma úgy küszöbölhető ki, ha új szál indítására és megállítására nem a Win32 API, hanem a C runtime library függvényeit használjuk. 6. oldal Többszálú programozás Windows NT alatt Ebben a fejezetben a Microsoft és a Borland által kínált alternatívákat vizsgáljuk meg. Más fordítók esetén a felhasználói kézikönyv alapján dönthetjük el, hogy el kell-e kerülnünk a CreateThread() és az ExitThread() függvények használatát, és ha igen, akkor erre milyen lehetőségek vannak. A Microsoft és a Borland fordítók egyaránt támogatják a beginthread() és az endthread() függvényeket, amelyekkel létrehozhatunk új szálat és meg is szüntethetjük azt. Bár a beginthread() általánosan használható

szálak kezelésére, mégsem rejt akkora lehetőségeket magában, mint a CreateThread(). Éppen ezért mind a Microsoft mind pedig a Borland kínálnak további NT specifikus alternatívákat. A következő szakaszokban ezeket mutatjuk be. A Microsoft alternatívák Microsoft Visual C++ esetén a beginthreadex() és az endthreadex() függvényeket használhatjuk a CreateThread() és az ExitThread() helyett. Ezek használatához szükséges a program elején elhelyezni az #include <process.h> sort A beginthreadex() prototípusa a következő: unsigned long beginthreadex(void *secAttr, unsigned stackSize, unsigned ( stdcall *threadFunc)(void ), void *param, unsigned flags, unsigned *threadID); Mint látható, a beginthreadex() paramétereinek sorrendje és jelentése azonos a CreateThread() paramétereivel. A secAttr paraméterrel a szálhoz tartozó biztonsági attribútumok állíthatók be Ha secAttr értéke NULL, akkor a függvény az alapértelmezett security

descriptort használja. Az új szálhoz tartozó stack mérete a stackSize változóban adható meg. Ha ez nulla, akkor az új szál stackjének mérete meg fog egyezni az őt létrehozó folyamat fő száljának stack méretével, és szükség esetén még tovább nőhet. A szálfüggvény címét (azaz tulajdonképpen a szál kezdetét) a threadFunc paraméterrel kell megadni. A beginthreadex()-hez tartozó szálfüggvény prototípusa a következő: unsigned stdcall threadfunc(void *param); Ez a prototípus funkcionálisan azonos a CreateThread()-hez tartozó szálfüggvény prototípusával, de más típusneveket használ. A szálfüggvénynek átadandó paraméter a beginthreadex() param változójában adott A flags a szál végrehajtási módját és állapotát határozza meg. Ha ez nulla, akkor a szál végrehajtása azonnal megkezdődik. Ha CREATE SUSPEND, akkor a szál felfüggesztett állapotban jön létre (Az ilyen szálat a később tárgyalandó ResumeThread()

függvény segítségével lehet elindítani.) A szál azonosítóját az threadID változóban kapjuk vissza. A függvény sikeres hívás esetén a szálhoz tartozó leíróval tér vissza; ha hiba lép fel, a visszatérési érték nulla. Az endthreadex() függvény prototípusa a következő: void endthreadex(unsigned status); Ez az ExitThread()-hez hasonlóan a status-ként megadott kilépési kóddal megszünteti a szálat. A beginthreadex() és az endthreadex() használata esetén nem szabad elfelejteni a programot hozzálinkelni a többszálas könyvtárhoz (multithreaded library). A Borland alternatívák Borland C++ fordító használata esetén új szál létrehozásához a beginthreadNT() függvény használható. A függvény használatához szükséges a PROCESS.H header fájl A beginthreadNT() prototípusa a következő: unsigned long beginthreadNT(void ( USERENTRY *threadFunc)(void ), unsigned stackSize, void *param, void *secAttr, unsigned long flags, 7. oldal

Többszálú programozás Windows NT alatt unsigned long *threadID); Ez a függvény pontosan úgy működik, mint a CreateThread() és a beginthreadex() csak éppen a paramétereinek sorrendje eltérő. A beginthreadNT()-vel létrehozott szál megszűntetéséhez az endthread() használható. A Microsofttól eltérően a Borland nem kínál újabb szál megállító függvényt. Az endthread() prototípusa: void endthread(void); Mint látható, ez a függvény nem ad vissza kilépési kódot. Ezen függvények használata esetén sem szabad elfelejteni a programot hozzálinkelni a többszálas könyvtárhoz. A Microsoft C/C++ szálakkal kapcsolatos függvényeinek használata Hogy bemutassuk ezeket a függvényeket, az előző programot úgy alakítottuk át, hogy a Microsoft beginthreadex() függvényeket használják. Mivel a CreateThread() és a beginthreadex() paramétereinek sorrendje és jelentése teljesen azonos, ezért ez az átalakítás igen egyszerűen

elvégezhető. A következő három lépés szükséges hozzá: 1. A PROCESS.H header fájl "beinklúdolása" 2. A szálfüggvények prototípusát kell úgy átírni, hogy a beginthreadex() előírásainak megfeleljen. 3. A CreateThread()-et helyett beginthreadex()-et kell írni. A programot még hozzá kell linkelni a többszálas könyvtárhoz (multithreaded library). A többszálas könyvtár kiválasztása Project Setting ablakban végezhető el. A következő program már az új változatot mutatja be /* Use Microsofts beginthreadex() function. */ #include <windows.h> #include <string.h> #include <stdio.h> #include <process.h> #include "thread.h" #define MAX 5000 LRESULT CALLBACK WindowFunc(HWND, UINT, WPARAM, LPARAM); /* use type names required by beginthreadex() / unsigned stdcall MyThread1(void * param); unsigned stdcall MyThread2(void * param); char szWinName[] = "MyWin"; /* name of window class / char

str[255]; /* holds output strings / DWORD Tid1, Tid2; /* thread IDs / int WINAPI WinMain(HINSTANCE hThisInst, HINSTANCE hPrevInst, LPSTR lpszArgs, int nWinMode) { HWND hwnd; MSG msg; WNDCLASSEX wcl; HANDLE hAccel; /* Define a window class. */ wcl.cbSize = sizeof(WNDCLASSEX); wcl.hInstance = hThisInst; /* handle to this instance / wcl.lpszClassName = szWinName; /* window class name / wcl.lpfnWndProc = WindowFunc; /* window function / wcl.style = 0; /* default style / wcl.hIcon = LoadIcon(NULL, IDI APPLICATION); wcl.hIconSm = LoadIcon(NULL, IDI APPLICATION); wcl.hCursor = LoadCursor(NULL, IDC ARROW); /* specify name of menu resource / wcl.lpszMenuName = "ThreadMenu"; /* main menu / 8. oldal Többszálú programozás Windows NT alatt wcl.cbClsExtra = 0; /* no extra / wcl.cbWndExtra = 0; /* information needed / /* Make the window white. */ wcl.hbrBackground = GetStockObject(WHITE BRUSH); /* Register the window class. */ if(!RegisterClassEx(&wcl)) return 0;

/* Now that a window class has been registered, a window can be created. */ hwnd = CreateWindow( szWinName, /* name of window class / "Demonstrate Threads", /* title / WS OVERLAPPEDWINDOW, /* window style - normal / CW USEDEFAULT, /* X coordinate - let Windows decide / CW USEDEFAULT, /* Y coordinate - let Windows decide / CW USEDEFAULT, /* width - let Windows decide / CW USEDEFAULT, /* height - let Windows decide / HWND DESKTOP, /* no parent window / NULL, /* no override of class menu / hThisInst, /* handle of this instance of the program / NULL /* no additional arguments / ); /* load accelerators / hAccel = LoadAccelerators(hThisInst, "ThreadMenu"); /* Display the window. */ ShowWindow(hwnd, nWinMode); UpdateWindow(hwnd); /* Create the message loop. */ while(GetMessage(&msg, NULL, 0, 0)) { if(!TranslateAccelerator(hwnd, hAccel, &msg)) { TranslateMessage(&msg); /* translate keyboard messages / DispatchMessage(&msg); /*

return control to Windows NT / } } return msg.wParam; } /* This function is called by Windows NT and is passed messages from the message queue. */ LRESULT CALLBACK WindowFunc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { int response; switch(message) { case WM COMMAND: switch(LOWORD(wParam)) { case IDM THREAD: /* create threads using beginthreadex() / beginthreadex(NULL, 0, MyThread1, (LPVOID) hwnd, 0, (unsigned *) &Tid1); beginthreadex(NULL, 0, MyThread2, (LPVOID) hwnd, 0, (unsigned *) &Tid2); break; case IDM EXIT: response = MessageBox(hwnd, "Quit the Program?","Exit", MB YESNO); if(response == IDYES) PostQuitMessage(0); 9. oldal Többszálú programozás Windows NT alatt break; case IDM HELP: MessageBox(hwnd, "F1: Help F2: Demonstrate Threads", "Help", MB OK); break; } break; case WM DESTROY: /* terminate the program / PostQuitMessage(0); break; default: /* Let Windows NT process any messages not specified in the

preceding switch statement. */ return DefWindowProc(hwnd, message, wParam, lParam); } return 0; } /* A thread of execution within the process. */ unsigned stdcall MyThread1(void * param) { int i; HDC hdc; for(i=0; i<MAX; i++) { sprintf(str, "Thread 1: loop # %5d ", i); hdc = GetDC((HWND) param); TextOut(hdc, 1, 1, str, strlen(str)); ReleaseDC((HWND) param, hdc); } return 0; } /* Another thread of execution within the process. */ unsigned stdcall MyThread2(void * param) { int i; HDC hdc; for(i=0; i<MAX; i++) { sprintf(str, "Thread 2: loop # %5d ", i); hdc = GetDC((HWND) param); TextOut(hdc, 1, 20, str, strlen(str)); ReleaseDC((HWND) param, hdc); } return 0; } A standard C könyvtári függvények elkerülése Sok többszálú programban könnyen elkerülhetőek a standard C könyvtári függvények. Ebben az esetben a CreateThread() és az ExitThread() használhatók anélkül, hogy memória szivárgás lépne fel. Például sok esetben mindössze két

standard C könyvtári függvényt használnak a programok, az sprintf()-et és az strlen()-t. Ezek helyettesítésére vannak Win32 függvények, mégpedig a wsprintf() és az lstrlen(). Ezek ráadásul még további lehetőségeket is kínálnak, amelyekkel Unicode karakterek is kezelhetők, egyébként pedig pontosan úgy működnek, mint a nekik megfelelő C könyvtári függvények. A Win32 még számos további string kezelő függvényre kínál alternatívát, ilyen például az lstrcat(), lstrcmp() és az lstrcpy(). Többféle karaktermanipuláló függvény is létezik, mint például CharUpper(), CharLower(), IsCharAlpha() és az IsCharAlphaNumeric(). Általában, ha csak egyszerűbb karakterkezelő C függvényeket használunk, akkor ezek lecserélhetők a Win32 nyújtotta függvényekre. A fejezet további példaprogramjaiban a 10. oldal Többszálú programozás Windows NT alatt CreateThread()-del hozunk létre új szálat, és az sprintf() illetve az strlen()

függvények helyett a wsprintf()-t illetve az lstrlen()-t használjuk. Ezáltal ezek a programok bármelyik olyan C/C++ compilerrel lefordíthatók, amely tud Windows NT 4 programot készíteni. Szál felfüggesztése és felélesztése Egy szál futása a SuspendThread() függvény meghívásával felfüggeszthető, ezután pedig a ResumeThread()del lehet újra elindítani. Ezeknek a prototípusa a következő: DWORD SuspendThread(HANDLE hThread); DWORD ResumeThread(HANDLE hThread); Mindkét függvény a szál leíróját a hThread paraméterben kapja meg. Minden szál futásához tartozik egy ún. felfüggesztési számláló (suspend count) Ha ennek a számlálónak az értéke nulla, akkor a szál nincs felfüggesztve, ha pedig nem nulla, akkor a szál felfüggesztett állapotban van. A SuspendThread() minden egyes hívása növeli ennek a számlálónak az értékét, a ResumeThread() hívások pedig csökkentik azt. Egy felfüggesztett szál csak akkor éled fel, ha a

számlálója elérte a nullát Ezért egy szál felélesztéséhez pontosan annyiszor kell meghívnunk a ResumeThread()-et, ahányszor előtte a SuspendThread()-et meghívtuk. Mindkét függvény a számláló előző állapotával tér vissza, hiba esetén pedig -1-gyel. A szálak prioritása Minden szálhoz tartozik egy beállított prioritás. Ez a prioritás határozza meg, hogy a szál mennyi processzoridőt kap. Alacsony prioritás szálak keveset, nagy prioritású szálak pedig sok processzoridőt kapnak Hogy egy szál mennyi processzoridőt kap, azt igencsak befolyásolja a karakterisztikája és a rendszerben futó többi szállal való kapcsolata. Egy szál tényleges prioritása két részből tevődik össze: egyrészt a folyamat prioritás osztályából, másrészt ehhez még hozzáadódik a szál saját prioritása. Most ezeket a komponenseket tekintjük át részletesen A prioritás osztályok A jelenlegi prioritás osztály lekérdezhető a GetPriorityClass()

függvénnyel, és beállítható a SetPriorityClass() meghívásával. Ezeknek a függvényeknek a prototípusa a következő: DWORD GetPriorityClass(HANDLE hApp); BOOL SetPriorityClass(HANDLE hApp, DWORD dwPriority); Itt hApp a folyamathoz tartozó leíró. A GetPriorityClass() az alkalmazáshoz tartozó prioritás oszály értékével tér vissza, hiba esetén pedig nullával. A SetPriorityClass() második paramétereként (dwPriority) a folyamat új prioritás osztálya adható meg. Ezek a prioritás osztályok léteznek, a legmagasabbtól, a legalacsonyabbig: REALTIME PRIORITY CLASS HIGH PRIORITY CLASS NORMAL PRIORITY CLASS IDLE PRIORITY CLASS A programok alapértelmezésben a NORMAL PRIORITY CLASS értéket kapják, és általában nincs szükség ennek megváltoztatására. Egy folyamat prioritásának a megváltoztatása ugyanis a rendszer összteljesítményére vonatkozóan negatív következményekkel is járhat. Például, ha egy program REALTIME PRIORITY CLASS prioritás

osztályt kap, akkor szinte ő fogja uralni a CPU-t. Ebben a fejezetben a példaprogramok mindig az alapértelmezett prioritást használják. 11. oldal Többszálú programozás Windows NT alatt A szál prioritás Minden egyes szálhoz megadható a prioritás osztályon belül egy saját prioritás is, amely meghatározza, hogy a folyamaton belül a szál mennyi CPU időt kapjon. A szál létrehozásakor normál prioritást kap, de persze ez a szál futása közben bármikor megváltoztatható. A szál jelenlegi prioritás értéke a GetThreadPriority() függvénnyel kérdezhető le. A SetThreadPriority() függvénnyel lehetőség van a szál prioritás növelésére vagy csökkentésére. Ezeknek a függvényeknek a prototípusa a következő: int GetThreadPriority(HANDLE hThread); BOOL SetThreadPriority(HANDLE hThread, int Priority); Mindkét függvény esetén hThread a szálhoz tartozó leíró. A SetThreadPriority() Priority paraméterében az új prioritás adható meg. A

GetThreadPriority() hiba esetén THREAD PRIORITY ERROR RETURN értékkel, egyébként pedig az aktuális szál prioritással tér vissza, amely a következő lehet (a legmagasabbtól a legkisebbig): Szál prioritás Érték THREAD PRIORITY TIME CRITICAL 15 THREAD PRIORITY HIGHEST 2 THREAD PRIORITY ABOVE NORMAL 1 THREAD PRIORITY NORMAL 0 THREAD PRIORITY BELOW NORMAL -1 THREAD PRIORITY LOWEST -2 THREAD PRIORITY IDLE -15 Ezek az értékek a folyamat prioritás osztályával együtt határozzák meg a szál tényleges prioritását. Így a Windows NT az alkalmazásoknak 31 különböző prioritási szintet definiál. A legtöbb esetben, ha egy szál normál prioritás osztályba tartozik, akkor nyugodtan lehet kísérletezni a szál prioritás megváltoztatásával anélkül, hogy ez a rendszer teljesítményét túlságosan negatívan befolyásolná. Mint látni fogjuk, a következő szakaszban bemutatásra kerülő Szálvezérlő panellel megváltoztathatjuk szál

prioritást (miközben a prioritás osztályt változatlanul hagyja). A Szálvezérlő panel elkészítése Ha többszálú programot készítünk, gyakran érdemes kísérletezni a különböző prioritás beállításokkal. Hasznos lehet, ha dinamikusan tudunk felfüggeszteni és elindítani, esetleg leállítani egy szálat. Mint látni fogjuk, az előzőekben ismertetett függvényekkel igen egyszerű egy erre a célra alkalmas Szálvezérlő panel elkészítése. Sőt mi több, ez a vezérlő panel a többszálú programunk futása közben is használható. Ezzel a vezérlő panellel két szál vezérelhető. Az egyszerűség kedvéért a vezérlő panelt egy modális dialógus ablakban valósítottuk meg, amelyet a program fő száljából indítunk el. Ez arra a globális szál leíróra épít, amelyet minden programban definiálni kell, amely a vezérlő panelt használja. A vezérlő panellel a következő műveletek végezhetők el: ♦ Szál prioritásának beállítása

♦ Szál futásának felfüggesztése ♦ Szál felélesztése ♦ Szál futásának megállítása A program közben mindkét szál prioritását kijelzi. Ahogy mondtuk, a vezérlő panel egy modális dialógus ablak. Mint tudjuk, egy modális dialógus ablak meghívásakor az alkalmazás többi része felfüggesztésre kerül egészen addig, amíg a felhasználó be nem zárja az 12. oldal Többszálú programozás Windows NT alatt ablakot. Viszont egy többszálú programban lehetőség van arra is, hogy egy modális dialógus ablak a saját száljában fusson. Ebben az esetben viszont a program többi része is aktív marad Ahogy említettük, a vezérlő panelt az őt használó program fő szálja indítja el, ezért a saját száljában fog futni. A megoldás további előnye, hogy egy modális ablakot könnyebb létrehozni, mint egy nem modálisat. Tehát mivel a dialógus ablak a saját száljában fog futni, ezért semmi előnyünk nem származna abból, ha nem

modális ablakot hoznánk létre. Ahogy egyre jobban megismerkedünk a többszálú programokkal látni fogjuk, hogy sok korábban igen nehezen kezelhető programozási kérdés egyszerűen megoldható velük. A Szálvezérlő panel program Itt egy példaprogram, amely tartalmazza a Szálvezérlő panelt és bemutatja a használatát. A vezérlő panelt a korábban bemutatott többszálú programunkban építettük be. A programban először indítsuk el a szálak futását a Threads menü Start Threads menüpontjának kiválasztásával, majd nyissuk meg a vezérlő panelt. Ezután kezdődhet a kísérletezés a prioritásokkal stb. /* Using a thread control panel / #include <windows.h> #include "panel.h" #define MAX 50000 #define NUMPRIORITIES 5 #define OFFSET 2 LRESULT CALLBACK WindowFunc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK ThreadPanel(HWND, UINT, WPARAM, LPARAM); DWORD WINAPI MyThread1(LPVOID param); DWORD WINAPI MyThread2(LPVOID param); char

szWinName[] = "MyWin"; /* name of window class / char str[255]; /* holds output strings / DWORD Tid1, Tid2; /* thread IDs / HANDLE hThread1, hThread2; /* thread handles / int ThPriority1, ThPriority2; /* thread priorities / int suspend1 = 0, suspend2 = 0; /* thread states / char priorities[NUMPRIORITIES][80] = { "Lowest", "Below Normal", "Normal", "Above Normal", "Highest" }; HINSTANCE hInst; int WINAPI WinMain(HINSTANCE hThisInst, HINSTANCE hPrevInst, LPSTR lpszArgs, int nWinMode) { HWND hwnd; MSG msg; WNDCLASSEX wcl; HANDLE hAccel; /* Define a window class. */ wcl.cbSize = sizeof(WNDCLASSEX); wcl.hInstance = hThisInst; /* handle to this instance / wcl.lpszClassName = szWinName; /* window class name / wcl.lpfnWndProc = WindowFunc; /* window function / wcl.style = 0; /* default style / 13. oldal Többszálú programozás Windows NT alatt wcl.hIcon = LoadIcon(NULL, IDI APPLICATION); wcl.hIconSm =

LoadIcon(NULL, IDI APPLICATION); wcl.hCursor = LoadCursor(NULL, IDC ARROW); /* specify name of menu resource / wcl.lpszMenuName = "ThreadPanelMenu"; /* main menu / wcl.cbClsExtra = 0; /* no extra / wcl.cbWndExtra = 0; /* information needed / /* Make the window white. */ wcl.hbrBackground = GetStockObject(WHITE BRUSH); /* Register the window class. */ if(!RegisterClassEx(&wcl)) return 0; /* Now that a window class has been registered, a window can be created. */ hwnd = CreateWindow( szWinName, /* name of window class / "Using a Thread Control Panel", /* title / WS OVERLAPPEDWINDOW, /* standard window / CW USEDEFAULT, /* X coordinate - let Windows decide / CW USEDEFAULT, /* Y coordinate - let Windows decide / CW USEDEFAULT, /* width - let Windows decide / CW USEDEFAULT, /* height - let Windows decide / HWND DESKTOP, /* no parent window / NULL, /* no override of class menu / hThisInst, /* handle of this instance of the program / NULL /*

no additional arguments / ); hInst = hThisInst; /* save instance handle / /* load accelerators / hAccel = LoadAccelerators(hThisInst, "ThreadPanelMenu"); /* Display the window. */ ShowWindow(hwnd, nWinMode); UpdateWindow(hwnd); /* Create the message loop. */ while(GetMessage(&msg, NULL, 0, 0)) { if(!TranslateAccelerator(hwnd, hAccel, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } return msg.wParam; } /* This function is called by Windows NT and is passed messages from the message queue. */ LRESULT CALLBACK WindowFunc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { int response; switch(message) { case WM COMMAND: switch(LOWORD(wParam)) { case IDM THREAD: /* create the threads / suspend1 = suspend2 = 0; hThread1 = CreateThread(NULL, 0, (LPTHREAD START ROUTINE)MyThread1, 14. oldal Többszálú programozás Windows NT alatt (LPVOID) hwnd, 0, &Tid1); hThread2 = CreateThread(NULL, 0, (LPTHREAD START ROUTINE)MyThread2, (LPVOID)

hwnd, 0, &Tid2); break; case IDM PANEL: /* activate control panel / DialogBox(hInst, "ThreadPanelDB", hwnd, (DLGPROC) ThreadPanel); break; case IDM EXIT: response = MessageBox(hwnd, "Quit the Program?", "Exit", MB YESNO); if(response == IDYES) PostQuitMessage(0); break; case IDM HELP: MessageBox(hwnd, "F1: Help F2: Start Threads F3: Panel", "Help", MB OK); break; } break; case WM DESTROY: /* terminate the program / PostQuitMessage(0); break; default: /* Let Windows NT process any messages not specified in the preceding switch statement. */ return DefWindowProc(hwnd, message, wParam, lParam); } return 0; } /* A thread of execution within the process. */ DWORD WINAPI MyThread1(LPVOID param) { int i; HDC hdc; for(i=0; i<MAX; i++) { wsprintf(str, "Thread 1: loop # %5d ", i); hdc = GetDC((HWND) param); TextOut(hdc, 1, 1, str, lstrlen(str)); ReleaseDC((HWND) param, hdc); } return 0; } /* Another thread of execution within

the process. */ DWORD WINAPI MyThread2(LPVOID param) { int i; HDC hdc; for(i=0; i<MAX; i++) { wsprintf(str, "Thread 2: loop # %5d ", i); hdc = GetDC((HWND) param); TextOut(hdc, 1, 20, str, lstrlen(str)); ReleaseDC((HWND) param, hdc); } 15. oldal Többszálú programozás Windows NT alatt return 0; } /* Thread control panel dialog box. */ LRESULT CALLBACK ThreadPanel(HWND hdwnd, UINT message, WPARAM wParam, LPARAM lParam) { long i; HANDLE hpbRes, hpbSus; switch(message) { case WM INITDIALOG: /* initialize list boxes / for(i=0; i<NUMPRIORITIES; i++) { SendDlgItemMessage(hdwnd, IDD LB1, LB ADDSTRING, 0, (LPARAM) priorities[i]); SendDlgItemMessage(hdwnd, IDD LB2, LB ADDSTRING, 0, (LPARAM) priorities[i]); } /* get current priority / ThPriority1 = GetThreadPriority(hThread1) + OFFSET; ThPriority2 = GetThreadPriority(hThread2) + OFFSET; /* update list box / SendDlgItemMessage(hdwnd, IDD LB1, LB SETCURSEL, (WPARAM) ThPriority1, 0); SendDlgItemMessage(hdwnd, IDD LB2,

LB SETCURSEL, (WPARAM) ThPriority2, 0); /* set suspend and resume buttons for first thread / hpbSus = GetDlgItem(hdwnd, IDD SUSPEND1); hpbRes = GetDlgItem(hdwnd, IDD RESUME1); if(suspend1) { EnableWindow(hpbSus, 0); /* disable Suspend / EnableWindow(hpbRes, 1); /* enable Resume / } else { EnableWindow(hpbSus, 1); /* enable Suspend / EnableWindow(hpbRes, 0); /* disable Resume / } /* set suspend and resume buttons for second thread / hpbSus = GetDlgItem(hdwnd, IDD SUSPEND2); hpbRes = GetDlgItem(hdwnd, IDD RESUME2); if(suspend2) { EnableWindow(hpbSus, 0); /* disable Suspend / EnableWindow(hpbRes, 1); /* enable Resume / } else { EnableWindow(hpbSus, 1); /* enable Suspend / EnableWindow(hpbRes, 0); /* disable Resume / } return 1; case WM COMMAND: switch(wParam) { case IDD TERMINATE1: TerminateThread(hThread1, 0); 16. oldal Többszálú programozás Windows NT alatt return 1; case IDD TERMINATE2: TerminateThread(hThread2, 0); return 1; case IDD SUSPEND1: SuspendThread(hThread1);

hpbSus = GetDlgItem(hdwnd, IDD SUSPEND1); hpbRes = GetDlgItem(hdwnd, IDD RESUME1); EnableWindow(hpbSus, 0); /* disable Suspend / EnableWindow(hpbRes, 1); /* enable Resume / suspend1 = 1; return 1; case IDD RESUME1: ResumeThread(hThread1); hpbSus = GetDlgItem(hdwnd, IDD SUSPEND1); hpbRes = GetDlgItem(hdwnd, IDD RESUME1); EnableWindow(hpbSus, 1); /* enable Suspend / EnableWindow(hpbRes, 0); /* disable Resume / suspend1 = 0; return 1; case IDD SUSPEND2: SuspendThread(hThread2); hpbSus = GetDlgItem(hdwnd, IDD SUSPEND2); hpbRes = GetDlgItem(hdwnd, IDD RESUME2); EnableWindow(hpbSus, 0); /* disable Suspend / EnableWindow(hpbRes, 1); /* enable Resume / suspend2 = 1; return 1; case IDD RESUME2: ResumeThread(hThread2); hpbSus = GetDlgItem(hdwnd, IDD SUSPEND2); hpbRes = GetDlgItem(hdwnd, IDD RESUME2); EnableWindow(hpbSus, 1); /* enable Suspend / EnableWindow(hpbRes, 0); /* disable Resume / suspend2 = 0; return 1; case IDOK: /* actually change priorities / ThPriority1 =

SendDlgItemMessage(hdwnd, IDD LB1, LB GETCURSEL, 0, 0); ThPriority2 = SendDlgItemMessage(hdwnd, IDD LB2, LB GETCURSEL, 0, 0); SetThreadPriority(hThread1, ThPriority1-OFFSET); SetThreadPriority(hThread2, ThPriority2-OFFSET); return 1; case IDCANCEL: EndDialog(hdwnd, 0); return 1; } } return 0; } A program fordításához szükséges a PANEL.H header fájl: #define IDM THREAD 100 #define IDM HELP 101 #define IDM PANEL 102 #define IDM EXIT 103 17. oldal Többszálú programozás Windows NT alatt #define IDD LB1 200 #define IDD LB2 201 #define IDD TERMINATE1 202 #define IDD TERMINATE2 203 #define IDD SUSPEND1 204 #define IDD SUSPEND2 205 #define IDD RESUME1 206 #define IDD RESUME2 207 #define IDD TEXT1 208 #define IDD TEXT2 209 #define IDD TEXT3 210 Továbbá szükséges még a következő erőforrás fájl: #include <windows.h> #include "panel.h" ThreadPanelMenu MENU { POPUP "&Threads" { MENUITEM "&Start

Threads F2", IDM THREAD MENUITEM "&Control Panel F3", IDM PANEL MENUITEM "E&xit F10", IDM EXIT } MENUITEM "&Help", IDM HELP } ThreadPanelDB DIALOG 20, 20, 170, 140 CAPTION "Thread Control Panel" STYLE DS MODALFRAME | WS POPUP | WS CAPTION | WS SYSMENU { DEFPUSHBUTTON "Change", IDOK, 80, 105, 33, 14, WS CHILD | WS VISIBLE | WS TABSTOP PUSHBUTTON "Done", IDCANCEL, 15, 120, 33, 14, WS CHILD | WS VISIBLE | WS TABSTOP PUSHBUTTON "Terminate 1", IDD TERMINATE1, 10, 10, 42, 12, WS CHILD | WS VISIBLE | WS TABSTOP PUSHBUTTON "Terminate 2", IDD TERMINATE2, 10, 60, 42, 12, WS CHILD | WS VISIBLE | WS TABSTOP PUSHBUTTON "Suspend 1", IDD SUSPEND1, 10, 25, 42, 12, WS CHILD | WS VISIBLE | WS TABSTOP PUSHBUTTON "Resume 1", IDD RESUME1, 10, 40, 42, 12, WS CHILD | WS VISIBLE | WS TABSTOP PUSHBUTTON "Suspend 2", IDD SUSPEND2, 10, 75, 42, 12, WS CHILD | WS VISIBLE | WS TABSTOP

PUSHBUTTON "Resume 2", IDD RESUME2, 10, 90, 42, 12, WS CHILD | WS VISIBLE | WS TABSTOP LISTBOX IDD LB1, 65, 11, 63, 42, LBS NOTIFY | WS VISIBLE | WS BORDER | WS VSCROLL | WS TABSTOP LISTBOX IDD LB2, 65, 61, 63, 42, LBS NOTIFY | WS VISIBLE | WS BORDER | WS VSCROLL | WS TABSTOP CTEXT "Thread 1", IDD TEXT1, 140, 22, 24, 18 CTEXT "Thread 2", IDD TEXT2, 140, 73, 24, 18 CTEXT "Thread Priority", IDD TEXT3, 65, 0, 64, 10 } ThreadPanelMenu ACCELERATORS { VK F2, IDM THREAD, VIRTKEY VK F3, IDM PANEL, VIRTKEY 18. oldal Többszálú programozás Windows NT alatt VK F10, IDM EXIT, VIRTKEY VK F1, IDM HELP, VIRTKEY } Néhány részlet a Szálvezérlő panellel kapcsolatban Nézzük meg közelebbről a szálvezérlő panel programot. Vegyük észre, hogy a program sok olyan globális változót definiált, amit a vezérlő panel is használ. Ezek a következők: DWORD Tid1, Tid2; /* thread IDs / HANDLE hThread1, hThread2; /* thread handles / int

ThPriority1, ThPriority2; /* thread priorities / int suspend1 = 0, suspend2 = 0; /* thread states / char priorities[NUMPRIORITIES][80] = { "Lowest", "Below Normal", "Normal", "Above Normal", "Highest" }; Itt Tid1 és Tid2 a két szál azonosítóját tartalmazza, hThread1 és hThread2 pedig a szálakhoz tartozó leírókat. Ezeket a leírókat a CreateThread() adja vissza, mikor létrehozzuk a szálakat. ThPriority1 és ThPriority2 a szálak jelenlegi prioritását tartalmazzák. A suspend1 és a suspend2 változókban tároljuk a szálak jelenlegi állapotát. A priorities változó szövegeket tartalmaz, amivel a vezérlő panel lista ablakait fogjuk inicializálni Ezek mutatják a szálak aktuális prioritási szintjét. A programban még definiálunk két makrót: #define NUMPRIORITIES 5 #define OFFSET 2 A NUMPRIORITES a szál által felvehető prioritási szintek darabszámát határozza meg. A vezérlő panel segítségével

az alábbi prioritási szintek állíthatók be: THREAD PRIORITY HIGHEST THREAD PRIORITY ABOVE NORMAL THREAD PRIORITY NORMAL THREAD PRIORITY BELOW NORMAL THREAD PRIORITY LOWEST A megmaradó két másik érték, a THREAD PRIORITY TIME CRITICAL és a THREAD PRIORITY IDLE nem állítható be, mert ezeknek igen kicsi gyakorlati hasznuk van. Például ha a valós idejű alkalmazás készítése a cél, akkor sokkal egyszerűbb a prioritás osztályát realtime-ra állítani. Természetesen könnyen módosítható a program úgy, hogy ezeket az értékeket is be lehessen állítani. Az OFFSET változó arra szolgál, hogy a lista ablakban szereplő elemek lista indexe és a prioritási értéke közötti különbséget tárolja. Mint említettük, a normál prioritás értéke nulla Ebben a példában a legmagasabb prioritás a THREAD PRIORITY HIGHEST, amelynek értéke 2, a legalacsonyabb prioritás pedig a THREAD PRIORITY LOWEST, amelynek értéke -2. Mivel a lista ablakban az elemek

sorszámozása nullával kezdődik, az OFFSET segítségével tudjuk átszámolni az indexet prioritás értékre. A vezérlő panel ablakkezelő függvénye a következő: /* Thread control panel dialog box. */ LRESULT CALLBACK ThreadPanel(HWND hdwnd, UINT message, WPARAM wParam, LPARAM lParam) { long i; HANDLE hpbRes, hpbSus; 19. oldal Többszálú programozás Windows NT alatt switch(message) { case WM INITDIALOG: /* initialize list boxes / for(i=0; i<NUMPRIORITIES; i++) { SendDlgItemMessage(hdwnd, IDD LB1, LB ADDSTRING, 0, (LPARAM) priorities[i]); SendDlgItemMessage(hdwnd, IDD LB2, LB ADDSTRING, 0, (LPARAM) priorities[i]); } /* get current priority / ThPriority1 = GetThreadPriority(hThread1) + OFFSET; ThPriority2 = GetThreadPriority(hThread2) + OFFSET; /* update list box / SendDlgItemMessage(hdwnd, IDD LB1, LB SETCURSEL, (WPARAM) ThPriority1, 0); SendDlgItemMessage(hdwnd, IDD LB2, LB SETCURSEL, (WPARAM) ThPriority2, 0); /* set suspend and resume buttons for first thread

/ hpbSus = GetDlgItem(hdwnd, IDD SUSPEND1); hpbRes = GetDlgItem(hdwnd, IDD RESUME1); if(suspend1) { EnableWindow(hpbSus, 0); /* disable Suspend / EnableWindow(hpbRes, 1); /* enable Resume / } else { EnableWindow(hpbSus, 1); /* enable Suspend / EnableWindow(hpbRes, 0); /* disable Resume / } /* set suspend and resume buttons for second thread / hpbSus = GetDlgItem(hdwnd, IDD SUSPEND2); hpbRes = GetDlgItem(hdwnd, IDD RESUME2); if(suspend2) { EnableWindow(hpbSus, 0); /* disable Suspend / EnableWindow(hpbRes, 1); /* enable Resume / } else { EnableWindow(hpbSus, 1); /* enable Suspend / EnableWindow(hpbRes, 0); /* disable Resume / } return 1; case WM COMMAND: switch(wParam) { case IDD TERMINATE1: TerminateThread(hThread1, 0); return 1; case IDD TERMINATE2: TerminateThread(hThread2, 0); return 1; case IDD SUSPEND1: SuspendThread(hThread1); hpbSus = GetDlgItem(hdwnd, IDD SUSPEND1); hpbRes = GetDlgItem(hdwnd, IDD RESUME1); EnableWindow(hpbSus, 0); /* disable Suspend /

EnableWindow(hpbRes, 1); /* enable Resume / 20. oldal Többszálú programozás Windows NT alatt suspend1 = 1; return 1; case IDD RESUME1: ResumeThread(hThread1); hpbSus = GetDlgItem(hdwnd, IDD SUSPEND1); hpbRes = GetDlgItem(hdwnd, IDD RESUME1); EnableWindow(hpbSus, 1); /* enable Suspend / EnableWindow(hpbRes, 0); /* disable Resume / suspend1 = 0; return 1; case IDD SUSPEND2: SuspendThread(hThread2); hpbSus = GetDlgItem(hdwnd, IDD SUSPEND2); hpbRes = GetDlgItem(hdwnd, IDD RESUME2); EnableWindow(hpbSus, 0); /* disable Suspend / EnableWindow(hpbRes, 1); /* enable Resume / suspend2 = 1; return 1; case IDD RESUME2: ResumeThread(hThread2); hpbSus = GetDlgItem(hdwnd, IDD SUSPEND2); hpbRes = GetDlgItem(hdwnd, IDD RESUME2); EnableWindow(hpbSus, 1); /* enable Suspend / EnableWindow(hpbRes, 0); /* disable Resume / suspend2 = 0; return 1; case IDOK: /* actually change priorities / ThPriority1 = SendDlgItemMessage(hdwnd, IDD LB1, LB GETCURSEL, 0, 0); ThPriority2 =

SendDlgItemMessage(hdwnd, IDD LB2, LB GETCURSEL, 0, 0); SetThreadPriority(hThread1, ThPriority1-OFFSET); SetThreadPriority(hThread2, ThPriority2-OFFSET); return 1; case IDCANCEL: EndDialog(hdwnd, 0); return 1; } } return 0; } A vezérlő panel megnyitásakor a következő lépéseket hajtja végre: 1. A két lista ablakot inicializálja. 2. Lekérdezi mindkét szál aktuális prioritás értékét. 3. Kiemeli a lista ablakokban az aktuális prioritás értékét. 4. Ha egy szál felfüggesztett állapotban van, akkor a hozzá tartozó Suspend gomb kikapcsolt állapotba kerül, ha éppen fut, akkor pedig a Resume gomb kerül kikapcsolt állapotba. Miután a dialógus ablak megjelent, a szál prioritásának megváltoztatásához először ki kell választani az új értéket a lista ablakból, majd pedig meg kell nyomni a Change gombot. Nem szabad elfelejteni, hogy a kiválasztott prioritás értéket addig nem kapja meg a szál, amíg a Change gombot meg nem nyomjuk. Egy

szálat a Suspend gomb megnyomásával függeszthetünk fel. A suspend1 és a suspend2 globális változók tárolják a szálak felfüggesztési állapotát. Nulla esetén a szál éppen fut, a nem nulla pedig azt jelenti, hogy a szál felfüggesztett állapotban van. Egy felfüggesztett szál a Resume gomb megnyomásával éleszthető fel A suspend1 és a suspend2 változók célja, hogy a dialógus ablak inicializálásakor a Suspend és a Resume gombok megfelelő állapotát beállítsák. Ne felejtsük el, hogy egy felélesztéséhez annyiszor kell meghívni a ResumeThread() függvényt, ahányszor a SuspendThread()-et meghívtuk. A Suspend gomb a megnyomása után kikapcsolt állapotba kerül, így egymás után többször nem lehet meghívni a SuspendThread()-et. Mivel a 21. oldal Többszálú programozás Windows NT alatt vezérlő panelt bármikor be lehet zárni és újra meg lehet nyitni, a megnyitáskor ezt a két gombot ennek a két változónak a segítségével

inicializáljuk. Ezeknek a változóknak az értéke új szálak indításakor törlődik Egy szál futása a Terminate gomb megnyomásával állítható meg. Ha egyszer egy szálat leállítottunk, akkor már többet nem indítható újra. Érdemes megjegyezni, hogy a vezérlő panel a TerminateThread() függvénnyel állítja le a szál futását. Mint azt korábban említettük, ezzel a függvénnyel óvatosan kell bánni Mielőtt továbblépnénk, érdemes jobban megismerkedni ezzel a programmal és megfigyelni, hogy a különböző prioritás értékek hogyan hatnak a szálak futására. Részletek A fő szál prioritásának megváltoztatása Az előző példaprogram nézegetése közben felmerülhetett egy fontos gondolat. Nevezetesen: a vezérlő panel miért csak a program által létrehozott két szál vezérléséről gondoskodik, és miért nem mind a hároméról? Mint tudjuk, minden program legalább egy szálból áll, ez pedig a fő szál. Ebben a példában a fő

szál a harmadik párhuzamosan futó szál. Azonban ezt a fő szálat a vezérlő panel nem tudja befolyásolni, és ennek két oka van Először is, általában nem szükséges a fő szál prioritásának a megváltoztatása. Erre a célra leggyakrabban inkább egy újabb szálat indítanak. A második ok, hogy nem minden művelet végezhető el a fő szálon Például ha felfüggesztjük a fő szál futását, akkor utána nem fogunk tudni kilépni a programból! És mivel a dialógus ablakot is a fő szál indítja el, ezért utána nem fogjuk tudni megnyitni a vezérlő panelt és feléleszteni a fő szálat. Ezen okok miatt a legtöbb esetben a fő szál változatlanul a THREAD PRIORITY NORMAL beállítással fut. Az előbbiek ellenére van mód a fő szál prioritásának lekérdezésére és átállítására. Ehhez először is szükség van a fő szálhoz tartozó leíróra. Ezt legkönnyebben a GetCurrentThread() függvénnyel kérdezhetjük le, melynek prototípusa a

következő: HANDLE GetCurrentThread(void); Ez a függvény az aktuális szálhoz tartozó ál-leíróval tér vissza. Ez az ál-leíró minden olyan helyen használható, ahol a normális szál leíróknak van értelme. A fő szál prioritásának hatását nyomon követhetjük, ha az előző kódban az IDM THREAD ágat úgy módosítjuk, hogy hThread2 a fő szálhoz tartozó leírót tartalmazza. A megváltoztatott kódrészlet a következő: case IDM THREAD: /* create the threads / hThread1 = CreateThread(NULL, 0, (LPTHREAD START ROUTINE)MyThread1, (LPVOID) hwnd, 0, &Tid1); CreateThread(NULL, 0, (LPTHREAD START ROUTINE)MyThread2, (LPVOID) hwnd, 0, &Tid2); /* assign hThread2 the handle of the main thread / hThread2 = GetCurrentThread(); break; Ezzel a változtatással a szálvezérlő panel indítása után a kettes számú szál a fő szál lesz. Ezért aztán legyünk óvatosak! Ha felfüggesztjük a fő szálat, nem fogunk tudni kilépni a programból! Szinkronizálás

Ha több szállal vagy folyamattal dolgozunk, előfordulhat, hogy a futásukat össze kell hangolnunk valamilyen módon. Ezt az eljárást szinkronizálásnak nevezik Ennek leggyakrabban használt esete az, amikor két szál szeretne hozzáférni egy erőforráshoz, de egyszerre csak az egyik használhatja. Például ha az egyik szál ír egy fájlba, akkor a másik szál ugyanezt vele egy időben nem teheti. Azt a mechanizmust, amivel ez elkerülhető, serialization-nak nevezik. A szinkronizálás másik gyakori esete, ha az egyik szál egy olyan eseményre vár, amit egy másik szál vált ki. Erre az esetre kell, hogy legyen néhány eszköz, amivel az első szál felfüggesztett állapotban tartható, amíg az esemény fellép, azután pedig fel kell éleszteni. 22. oldal Többszálú programozás Windows NT alatt Mielőtt elkezdenénk, definiáljuk azt a két állapotot, amiben egy taszk lehet. Az első: a taszk futhat - vagy legalábbis futásra kész állapotban van, és ha

megkapja az időszeletét, akkor futni kezd. A második: a taszk lehet blokkolt, azaz felfüggesztett állapotban várhat egy erőforrásra vagy egy eseményre egészen addig, amíg az erőforrás felszabadul, vagy az esemény fellép. A következő fejezet a szinkronizálás és a serialization problémával foglalkozik, és ennek leggyakoribb megoldásával, a szemaforral. A szinkronizációs probléma lényege A Windows NT számos olyan szolgáltatást nyújt, amivel egy megosztott erőforráshoz férhetünk hozzá - ezen segítség nélkül nincs mód arra, hogy egy szál vagy egy folyamat megállapítsa, vajon csak egyedül ő használja-e az erőforrást. Ennek megértéséhez képzeljük el, hogy olyan multitaszkos operációs rendszerre írunk programot, amely egyáltalán nem támogatja a szinkronizálást. Továbbá tegyük fel, hogy van két folyamatunk A és B, amelyek időről időre használnak egy olyan R erőforrást (mondjuk egy fájlt), amelyhez egyszerre csak az

egyikük férhet hozzá. Hogy az egyik folyamat ne férhessen hozzá az erőforráshoz, amíg a másik folyamat használja, alkalmazzuk a következő megoldást: Hozzunk létre egy olyan flag nevű változót, amelyhez mindkét folyamat hozzáfér, és ennek kezdeti értéke legyen nulla. Ezek után, ha egy folyamat hozzá akar férni R-hez, akkor előtte várnia kell a flag-re, aztán át kell állítania, és csak ezután használhatja az erőforrást. Ha már nem igényli tovább az erőforrást, akkor a flag-et törölnie kell. A következő kódrészlet ezt mutatja be: while(flag) ; /* vár a flag-re / flag = 1; /* beállítja a flag-et / /* . az R erőforrás használata */ flag = 0; /* a flag törlése/ A megoldás lényege, hogy egyik folyamat sem használhatja az erőforrást, ha a flag beállított állapotban van. Legalábbis valahogy így kell megoldani a problémát. Ugyanis a dolog még hagy némi kívánnivalót maga után: ez ugyanis nem mindig működik!

Nézzük meg miért! A fenti kód nem zárja ki, hogy a két folyamat egyszerre használja az R erőforrást. A while ciklus újra és újra betölt és összehasonlít egy változót. Más szavakkal: teszteli a flag értékét Ha a flag szabad, akkor a kód következő sora beállítja a flag értékét. A baj csak az, hogy semmi nem tiltja meg azt, hogy ez a két művelet két különböző időszeletben kerüljön végrehajtásra. E között a két időszelet között a flag-et másik folyamat is elérheti, így előfordulhat, hogy egyszerre két folyamat fog hozzáférni az erőforráshoz. Ennek megértéséhez képzeljük el, hogy az A folyamat a while ciklusban azt találja, hogy a flag értéke nulla, azaz szabad az R erőforrás. Csakhogy mielőtt beállíthatná 1-re, az időszelete lejár, és B folyamat futása folytatódik A B-nek is lefut a while ciklusa, és ő is szabadnak fogja találni az R erőforrást. Miután az A visszakapja a vezérlést, ő is használni fogja

R-t. A megoldás kritikus pontja, hogy a flag tesztelése és beállítása nem alkot egyetlen megszakíthatatlan műveletet, hanem mint ahogy azt a fenti példa is mutatta más időszeletbe is kerülhetnek. Akárhogy is próbálkozunk, nincs a problémára olyan megoldás, amely csak alkalmazás szintű kódot használna. A szinkronizálás megoldása egyszerű és elegáns. Az operációs rendszer (esetünkben a Windows NT) nyújt egy olyan eljárást, amely teszteli, és szabad jelzés esetén beállítja a flag-et. Ezt az operációs rendszer készítői test and set műveletnek nevezik. Történelmi okokból a szálak és folyamatok között szinkronizálásra használt flag-et szemafornak hívják. A szemafor a Windows NT szinkronizáló rendszerének magjában található A Windows NT szinkronizáló objektumai A Windows NT ötféle szinkronizáló objektummal segíti a programozást. Az első típus a klasszikus szemafor A szemafor arra használható, hogy egyszerre csak

meghatározott számú szál vagy folyamat férhessen hozzá az erőforráshoz. A szemafor tulajdonképpen egy számláló, amelynek az értéke dekrementálódik, ha egy taszk használja a szemafort, és inkrementálódik, ha a taszk tovább nem igényli. 23. oldal Többszálú programozás Windows NT alatt A második objektum a mutex 1. A mutexet abban az esetben célszerű használni, ha azt szeretnénk, hogy egyszerre egy és csakis egy szál vagy folyamat használhassa az erőforrást. Lényegében a mutex a klasszikus szemafor speciális esete. A harmadik megoldás a szinkronizálásra az esemény objektum (event object) használata. Ebben az esetben az erőforrás blokkolva van egészen addig, amíg egy másik folyamat vagy szál nem jelzi, hogy szabad használni. Az esemény objektum jelzi, hogy a megadott esemény bekövetkezett. A negyedik lehetőség a stopper (waitable timer) használata. A stopper megadott időre felfüggeszti egy szál futását. Ez új lehetőség a

Windows NT 4-es verzióban, és sok érdekes lehetőséget rejt magában, amit főként háttérben futó taszkoknál lehet jól kihasználni. A kritikus szakasz objektum (critical section object) használatával van lehetőség arra, hogy a kód egy részét egyszerre csak az egyik szál használja. Ha már az egyik szál belépett a kritikus szakaszba, akkor a másik szál addig nem lép be, amíg az előző el nem hagyja. (Kritikus szakasz csak a folyamaton belül szálakra alkalmazható.) A kritikus szakasz kivételével a fenti objektumok használhatók mind folyamatok, mind pedig a folyamaton belül szálak szinkronizálására. A valóságban a szemafor a szálak közötti kommunikáció leggyakrabban használt és legegyszerűbb módja. Ebben a fejezetben bemutatjuk, hogyan lehet szemafort, esemény objektumot és stoppert létrehozni illetve használni. Ezek megértése után a mutexet és a kritikus szakaszt bárki maga is könnyen megértheti Ne felejtsük el, hogy a

szinkronizálás magja, akármelyik módot is választjuk, kimondva vagy kimondatlanul a szemafor, ezért először ezt nézzük meg részletesen. Szálak szinkronizálása szemafor segítségével Mielőtt használhatnánk a szemafort, létre kell hozni a CreateSemaphore() utasítással, melynek prototípusa a következő: HANDLE CreateSemaphore(LPSECURITY ATTRIBUTES lpSecAttr, LONG InitialCount, LONG MaxCount, LPSTR lpszName); Itt lpSecAttr a biztonsági attribútumokra mutató pointer. Ha lpSecAttr értéke NULL, akkor a függvény az alapértelmezett security descriptort használja. A szemafor egyszerre egy vagy több taszk számára tesz elérhetővé egy objektumot. Ezen taszkok maximális számát a MaxCount paraméterrel adhatjuk meg. Ha ez 1, akkor a szemafor mutexként működik, azaz egyszerre egy és csakis egy szál vagy folyamat férhet hozzá az erőforráshoz. A szemaforok egy számláló segítségével követik nyomon, hogy jelenleg éppen hányan használják az

erőforrást. Ha ez a számláló nulla, akkor további hozzáférés nem engedélyezett egészen addig, amíg egy taszk le nem mond a szemaforról (és így az erőforrásról). Ha a számláló értéke nullánál nagyobb, akkor még érkezhet új igény az erőforrás használatára. Minden alkalommal, ha egy taszk engedélyt kap az erőforrás használatára, a számláló értéke eggyel csökken. A szemafor kezdeti értéke az InitialCount-tal megadható Ha ez nulla, akkor kezdetben minden szemaforra váró objektum blokkolva lesz addig, amíg a program más részében valaki el nem engedi a szemafort. Az InitialCount tipikus értéke 1 vagy több, jelezve, hogy legalább egy taszk hozzáférhet a szemaforhoz. Az InitialCount-nak minden esetben nem-negatívnak, és a MaxCount-nál kisebbnek vagy egyenlőnek kell lennie. Az lpszName a szemafor nevét tartalmazó stringre mutató pointer. A szemaforok globális objektumok, amelyeket más folyamatok is használhatnak. Azaz ha két

folyamat ugyanolyan nevű szemafort nyit meg, akkor tulajdonképpen ugyanazt a szemafort használják. Ezáltal lehet két folyamatot szinkronizálni A név lehet NULL is, ebben az esetben a szemafor az adott folyamaton belül lokalizált. Ha lpszName egy már létező szemafornévre mutat, akkor az InitialCount és a MaxCount paramétereknek nincs jelentőségük. 1 A mutex szó a mutual exclusion, azaz a kölcsönös kizárás szavak összevonásából származik. 24. oldal Többszálú programozás Windows NT alatt A CreateSemaphore() sikeres hívás esetén a szemaforra mutató leíróval tér vissza, hiba esetén pedig NULL-lal. Miután létrehoztuk a szemafort, a WaitForSingleObject() és a ReleaseSemaphore() függvényekkel használhatjuk. Ezeknek a prototípusa a következő: DWORD WaitForSingleObject(HANDLE hObject, DWORD dwHowLong); BOOL ReleaseSemaphore(HANDLE hSema,LONG Count,LPLONG lpPrevCount); A WaitForSingleObject() vár a szemaforra (illetve bármilyen más

típusú szinkronizáló objektumra). Ez a függvény addig nem tér vissza, amíg az adott objektum szabad nem lesz, vagy a megadott timeout le nem telik. Szemafor esetén hObject a korábban létrehozott szemaforhoz tartozó leíró. A dwHowLong paraméterben adható meg milliszekundumban az az idő, ameddig a függvény vár. Ha ez az idő letelik, akkor a függvény timeout hibával tér vissza. Ha dwHowLong értéke INFINITE, akkor a függvény a végtelenségig várhat, nincs timeout A függvény WAIT OBJECT 0-val tér vissza sikeres hívás esetén (azaz ha megkapja a szemafort), és WAIT TIMEOUT-tal, ha a timeout idő letelt. Minden sikeres WaitForSingleObject() hívás esetén a szemafor számlálója dekrementálódik. A ReleaseSemaphore() lemond a szemaforról, hogy másik szál is használhassa. Itt hSema a szemaforhoz tartozó leíró. A Count határozza meg, hogy a szemafor számlálóját mennyivel kell növelni Ez tipikusan 1 szokott lenni Az lpPrevCount a szemafor

számlálójának előző állását tartalmazó változóra mutató pointer. Ha erre nincs szükség, akkor NULL-t célszerű megadni. A függvény sikeres hívás esetén nem-nullával, hiba esetén pedig nullával tér vissza. A következő program a szemafor használatát mutatja be. Az előző többszálú programot írtuk át úgy, hogy a két szál ne párhuzamosan fusson. Érdemes megfigyelni, hogy a szemaforhoz tartozó leírót globális területen hoztuk létre, így mindegyik szál (a főszál is) használhatja. Ez a program a korábban már leírt header és erőforrás fájlokat használja. /* A multithreaded program that uses a semaphore. */ #include <windows.h> #include "thread.h" #define MAX 5000 LRESULT CALLBACK WindowFunc(HWND, UINT, WPARAM, LPARAM); DWORD WINAPI MyThread1(LPVOID param); DWORD WINAPI MyThread2(LPVOID param); char szWinName[] = "MyWin"; /* name of window class / char str[255]; /* holds output strings / DWORD Tid1,

Tid2; /* thread IDs / HANDLE hSema; /* handle to semaphore / int WINAPI WinMain(HINSTANCE hThisInst, HINSTANCE hPrevInst, LPSTR lpszArgs, int nWinMode) { HWND hwnd; MSG msg; WNDCLASSEX wcl; HANDLE hAccel; /* Define a window class. */ wcl.cbSize = sizeof(WNDCLASSEX); wcl.hInstance = hThisInst; /* handle to this instance / wcl.lpszClassName = szWinName; /* window class name / wcl.lpfnWndProc = WindowFunc; /* window function / wcl.style = 0; /* default style / 25. oldal Többszálú programozás Windows NT alatt wcl.hIcon = LoadIcon(NULL, IDI APPLICATION); wcl.hIconSm = LoadIcon(NULL, IDI APPLICATION); wcl.hCursor = LoadCursor(NULL, IDC ARROW); /* specify name of menu resource / wcl.lpszMenuName = "ThreadMenu"; /* main menu / wcl.cbClsExtra = 0; /* no extra / wcl.cbWndExtra = 0; /* information needed / /* Make the window white. */ wcl.hbrBackground = GetStockObject(WHITE BRUSH); /* Register the window class. */ if(!RegisterClassEx(&wcl)) return 0;

/* Now that a window class has been registered, a window can be created. */ hwnd = CreateWindow( szWinName, /* name of window class / "Use a Semaphore", /* title / WS OVERLAPPEDWINDOW, /* window style - normal / CW USEDEFAULT, /* X coordinate - let Windows decide / CW USEDEFAULT, /* Y coordinate - let Windows decide / CW USEDEFAULT, /* width - let Windows decide / CW USEDEFAULT, /* height - let Windows decide / HWND DESKTOP, /* no parent window / NULL, /* no override of class menu / hThisInst, /* handle of this instance of the program / NULL /* no additional arguments / ); /* load accelerators / hAccel = LoadAccelerators(hThisInst, "ThreadMenu"); /* Display the window. */ ShowWindow(hwnd, nWinMode); UpdateWindow(hwnd); /* Create the message loop. */ while(GetMessage(&msg, NULL, 0, 0)) { if(!TranslateAccelerator(hwnd, hAccel, &msg)) { TranslateMessage(&msg); /* translate keyboard messages / DispatchMessage(&msg); /* return

control to Windows NT / } } return msg.wParam; } /* This function is called by Windows NT and is passed messages from the message queue. */ LRESULT CALLBACK WindowFunc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { int response; 26. oldal Többszálú programozás Windows NT alatt switch(message) { case WM CREATE: hSema = CreateSemaphore(NULL, 1, 1, NULL); break; case WM COMMAND: switch(LOWORD(wParam)) { case IDM THREAD: CreateThread(NULL, 0, (LPTHREAD START ROUTINE)MyThread1, (LPVOID) hwnd, 0, &Tid1); CreateThread(NULL, 0, (LPTHREAD START ROUTINE)MyThread2, (LPVOID) hwnd, 0, &Tid2); break; case IDM EXIT: response = MessageBox(hwnd, "Quit the Program?", "Exit", MB YESNO); if(response == IDYES) PostQuitMessage(0); break; case IDM HELP: MessageBox(hwnd, "F1: Help F2: Demonstrate Threads", "Help", MB OK); break; } break; case WM DESTROY: /* terminate the program / PostQuitMessage(0); break; default: /* Let Windows NT process

any messages not specified in the preceding switch statement. */ return DefWindowProc(hwnd, message, wParam, lParam); } return 0; } /* A thread of execution within the process. */ DWORD WINAPI MyThread1(LPVOID param) { int i; HDC hdc; /* wait for access to be granted / if(WaitForSingleObject(hSema, 10000)==WAIT TIMEOUT) { MessageBox((HWND)param, "Time Out Thread 1", "Semaphore Error", MB OK); return 0; } for(i=0; i<MAX; i++) { if(i==MAX/2) { /* Release at half way point. This allows MyThread2 to run. */ ReleaseSemaphore(hSema, 1, NULL); /* Next, once again wait for access to be granted. */ if(WaitForSingleObject(hSema, 10000)==WAIT TIMEOUT) { MessageBox((HWND)param, "Time Out Thread 1", "Semaphore Error", MB OK); 27. oldal Többszálú programozás Windows NT alatt return 0; } } wsprintf(str, "Thread 1: loop # %5d ", i); hdc = GetDC((HWND) param); TextOut(hdc, 1, 1, str, lstrlen(str)); ReleaseDC((HWND) param, hdc); }

ReleaseSemaphore(hSema, 1, NULL); return 0; } /* Another thread of execution within the process. */ DWORD WINAPI MyThread2(LPVOID param) { int i; HDC hdc; /* wait for access to be granted / if(WaitForSingleObject(hSema, 10000)==WAIT TIMEOUT) { MessageBox((HWND)param, "Time Out Thread 2", "Semaphore Error", MB OK); return 0; } for(i=0; i<MAX; i++) { wsprintf(str, "Thread 2: loop # %5d ", i); hdc = GetDC((HWND) param); TextOut(hdc, 1, 20, str, lstrlen(str)); ReleaseDC((HWND) param, hdc); } ReleaseSemaphore(hSema, 1, NULL); return 0; } A szemafor program részletei Ebben a programban hSema a két szál szinkronizálására szolgáló szemaforhoz tartozó leíró. A futtatáskor MyThread1()-t előbb hozzuk létre, mint MyThread2()-t, ezért előbb szerzi meg a szemafort és kezdi el a végrehajtást. Amikor MyThread2() létre jön, nem tud hozzáférni a szemaforhoz, ezért várakozni kényszerül Eközben mikor MyThread1() for ciklusa eléri a MAX/2

értéket, felszabadítja a szemafort. Miután MyThread2() megkapja a szemafort, MyThread1() fog várakozni. Végül MyThread2() fejezi be a futását, és MyThread1() feléledhet. Íme néhány tipp, amit érdemes kipróbálni. Mivel ez a szemafor egyszerre úgyis csak egyetlen szálat enged hozzáférni az erőforráshoz, ezért helyettesíthetjük mutex szemaforral is. Továbbá érdemes úgy átírni a programot, hogy a szálakból több példányt hozunk létre, és növeljük a hSema leíróhoz tartozó számlálót is, hogy egyszerre többen is használhassák a szemafort. 28. oldal Többszálú programozás Windows NT alatt Esemény objektum használata Mint azt korábban említettük, az esemény objektummal értesíteni lehet egy szálat vagy egy folyamatot egy esemény bekövetkeztéről. Esemény objektumot a CreateEvent() API függvénnyel lehet létrehozni, melynek prototípusa a következő: HANDLE CreateEvent(LPSECURITY ATTRIBUTES lpSecAttr, BOOL Manual, BOOL

Initial, LPSTR lpszName); Itt lpSecAttr a biztonsági attribútumokra mutató leíró, ha ez NULL, akkor a függvény az alapértelmezett security descriptort használja. A Manual értéke határozza meg, hogy az esemény hogyan befolyásolja az esemény objektumot. Ha Manual értéke nem-nulla, akkor az esemény objektum csak a ResetEvent() függvénnyel resetelhető. Egyébként pedig az esemény objektum automatikusan alaphelyzetbe áll, miután egy blokkolt szál szabad utat kap. Az Initial paraméter az esemény objektum kezdeti állapotát határozza meg Ha nem-nulla, akkor az esemény objektum beállított állapotban van, ha pedig nulla, akkor alaphelyzetben. Az lpszName az esemény objektum nevét tartalmazó stringre mutat. Az esemény objektumok globálisak, tehát már folyamatok is használhatják őket. Ezért ha két folyamat azonos nevű esemény objektumot nyit meg, akkor tulajdonképpen ugyanazt az objektumot használják. Ez ad lehetőséget a két folyamat

szinkronizálására Ha a név NULL, akkor az objektum a folyamaton belül lokálisan jön létre. A CreateEvent() sikeres hívás esetén az esemény objektumhoz tartozó leíróval tér vissza, egyébként pedig NULL-lal. Miután létrehoztuk, az eseményre váró folyamat a WaitForSingleObject() meghívásával használhatja az esemény objektumot. Itt első paraméternek az esemény objektum leíróját kell megadni Ennek hatására a szál futása az esemény bekövetkeztéig felfüggesztődik. Az esemény bekövetkeztés a SetEvent() függvénnyel lehet jelezni: BOOL SetEvent(HANDLE hEventObject); Itt hEventObject a már korábban létrehozott esemény objektumhoz tartozó leíró. Ennek a függvénynek a hatására az első szál vagy folyamat, amelyik korábban meghívta a WaitForSingleObject() függvényt, feléled, és a futása folytatódik. Az esemény objektum működésének megismeréséhez módosítsuk az előző programot a következőképpen. Először is,

deklaráljunk egy hEvent nevű globális leírót. Azután a következő sort adjuk hozzá a WM CREATE case ághoz: hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); Végül pedig módosítsuk MyThread1()-t és MyThread2()-t úgy, hogy a következőképpen nézzenek ki: /* First thread of execution. */ DWORD WINAPI MyThread1(LPVOID param) { int i; HDC hdc; /* wait for access to be granted / if(WaitForSingleObject(hEvent, 10000)==WAIT TIMEOUT) { MessageBox((HWND)param, "Time Out Thread 1", "Event Error", MB OK); return 0; } for(i=0; i<MAX; i++) { wsprintf(str, "Thread 1: loop # %5d ", i); hdc = GetDC((HWND) param); 29. oldal Többszálú programozás Windows NT alatt TextOut(hdc, 1, 1, str, lstrlen(str)); ReleaseDC((HWND) param, hdc); } return 0; } /* Second thread of execution. */ DWORD WINAPI MyThread2(LPVOID param) { int i; HDC hdc; for(i=0; i<MAX; i++) { wsprintf(str, "Thread 2: loop # %5d ", i); hdc = GetDC((HWND) param);

TextOut(hdc, 1, 20, str, lstrlen(str)); ReleaseDC((HWND) param, hdc); } /* send event notification / SetEvent(hEvent); return 0; } Ezek után, ha futtatjuk a programot, MyThread1() blokkolt állapotban lesz egészen addig, amíg MyThread2() futása be nem fejeződik, és ezt nem jelzi a másik szálnak. Waitable timer használata A waitable timer újdonság a Windows NT 4-es verzióban. Bár timer minden Windows verzióban van, korábban nem volt közvetlen szerepük a szinkronizálásban. Bár lehetséges szemafort és a standard timert összekapcsolni, a waitable timer használata ezt mégis sokkal kényelmesebbé teszi. A waitable timer segítségével sokkal könnyebbé válik a háttérben futó alkalmazások automatizálása. Hordozhatóság: A waitable timer először a Windows NT 4-es verzióban került bevezetésre, ezért a Windows NT 3.51, a Windows 95 és a Windows 31 verziók nem támogatják Mikor létrehozunk egy waitable timert, tulajdonképpen egy olyan timert

készítünk, amelyik egy meghatározott ideig a háttérben fut. A szál a WaitForSingleObject() függvénnyel használhatja a timert A szál egészen addig blokkolt állapotban lesz, amíg a timer le nem jár. Waitable timert a CreateWaitableTimer() függvénnyel lehet létrehozni, melynek prototípusa a következő: HANDLE CreateWaitableTimer(LPSECURITY ATTRIBUTES lpSecAttr, BOOL manual, LPCSTR lpszName); Itt lpSecAttr a waitable timerhez tartozó security descriptorra mutató leíró. Ha NULL, akkor a függvény az alapértelmezett security descriptort használja. Ha a manual paraméter értéke nem-nulla, akkor a timert minden periódus után újra kell indítani, ha pedig nulla, akkor automatikusan újraindul. Az lpszName a timer nevét 30. oldal Többszálú programozás Windows NT alatt tartalmazó stringre mutató pointer. Ha erre nincs szükség, akkor az értéke lehet NULL is A névvel rendelkező timereket több folyamat is használhatja, míg a névtelenek a

folyamaton belül lokalizálódnak. Sikeres hívás esetén a függvény a timerhez tartozó leíróval tér vissza, egyébként pedig NULL-lal. A timer létrehozásakor inaktív állapotban van. Felhízni a SetWaitableTimer() függvénnyel lehet, melynek prototípusa a következő: BOOL SetWaitableTimer(HANDLE hWaitTimer, const LARGE INTEGER *TargetTime, LONG period, PTIMERAPCROUTINE lpTimerFunc, LPVOID param, BOOL Unsuspend); Itt hWaitTimer a timer objektumhoz tartozó leíró. A TargetTime változóban adható meg, hogy mennyi idő után járjon le. A period paraméterben a timer két felhúzása közötti időközt kell megadni Ha a period nulla, akkor a timer csak egyszer jár le. Az lpTimerFunc egy függvényre mutat, ami a timer lejárásakor hívódik meg Ez a paraméter opcionális, ha ilyen függvényre nincs szükség, akkor NULL-t kell megadni. A param változóban megadott értéket a függvény átadja az lpTimerFunc-ként definiált timer függvénynek. Ha Unsuspend

nem-nulla, akkor az alacsony fogyasztású üzemmódban lévő számítógép felélesztésre kerül. A SetWaitableTimer() sikeres hívás esetén nem-nullával, egyébként pedig nullával tér vissza. A timer függvénynek a következő prototípussal kell rendelkeznie: VOID (APIENTRY *PTIMERAPCROUTINE TimerFunc(LPVOID param, DWORD dwLowTime, DWORD dwHighTime); Itt param az az érték, amit a függvény a SetWaitableTimer() függvénytől kap. A dwLowTime és a dwHighTime a lejárási időt tartalmazzák a FILETIME struktúrában megadottal kompatíbilis formában. (Ezt később ismertetjük.) Őszintén szólva a legtöbb waitable timert használó alkalmazásnak egyáltalán nincs szüksége ilyen timer függvényre. A SetWaitableTimer() meghívásakor a cél időt LARGE INTEGER típusban kell megadni. Ez 64-bites integerként tárolja az időt, ami teljesen kompatíbilis a FILETIME formátumával. Ezek részletesen a következők: typedef struct FILETIME { DWORD dwLowDateTime;

// az alsó 32-bit DWORD dwHighDateTime; // a felső 32-bit. } FILETIME; typedef union LARGE INTEGER { struct { DWORD LowPart; LONG HighPart; } LONGLONG QuadPart; } LARGE INTEGER; A FILETIME struktúra az 1601. január 1-je óta eltelt időt tartalmazza 100 nanoszekundumos egységekben A Win32 tartalmaz olyan függvényeket, amelyekkel a FILETIME által mutatott időt kényelmesebb formára tudjuk konvertálni. Talán a legegyszerűbb mód a waitable timer idejének beállítására a SYSTEMTIME struktúra használata, amit aztán a FILETIME formátumára konvertálhatunk (ami azután már lehet LARGE INTEGER). A SYSTEMTIME struktúra definíciója a következő: typedef struct SYSTEMTIME { 31. oldal Többszálú programozás Windows NT alatt WORD wYear; // év WORD wMonth; // hónap WORD wDayOfWeek; // a hét napjának száma: 0.6 WORD wDay; // melyik nap a hónapban: 0.31 WORD wHour; // óra WORD wMinute; // perc WORD wSecond; // másodperc WORD

wMilliseconds; // ezredmásodperc } SYSTEMTIME; Miután a SYSTEMTIME struktúra mezőit beállítottuk, meghívhatjuk a SystemTimeToFileTime() függvényt, ami az időt FILETIME alakra hozza. Ennek a függvénynek a prototípusa a következő: BOOL SystemTimeToFileTime(CONST SYSTEMTIME *lpSysTime, FILETIME *lpFileTime); A függvény sikeres hívás esetén nem-nullával, egyébként pedig nullával tér vissza. A legtöbb esetben nincs szükség arra, hogy a SYSTEMTIME struktúra mezőit egyesével állítgassuk be. Legtöbbször elég lekérdezni az aktuális rendszeridőt, majd pedig ezt a kívánt értékkel megnövelni. Például ha egy olyan timert szeretnénk, amelyik egy óra múlva jár le, akkor lekérdezzük a rendszeridőt, majd a wHour mező értékét megfelelően megnöveljük. A rendszeridőt a GetSystemTime() függvénnyel lehet lekérdezni: VOID GetSystemTime(SYSTEMTIME *lpSysTime); A jelenlegi rendszeridőt az lpSysTime-ként megadott struktúrában adja vissza a

függvény. Az idő UTC (Coordinated Universal Time) formában adott, ami lényegében megegyezik a greenwichi középidővel (GMT). A következő program a waitable timer használatát mutatja be. Az F2 billentyű minden egyes lenyomásakor új szál keletkezik, azon belül pedig egy waitable timer 10 másodperces idővel. Ezután az ablak ikon állapotba megy, és a szál vár a timerre. Miután a timer lejárt, az ablak ismét normál méretű lesz, és a szál futása folytatódik. Érdemes megfigyelni a Win32 WINNT definíciót a program elején Mivel a waitable timerek elég újak, ez a definíció biztosítja a fordítót arról, hogy a megfelelő header sorok megvannak. /* Demonstrate a waitable timer / /* The following is needed to ensure that waitable timer API functions are available. */ #define WIN32 WINNT 0x0400 #include <windows.h> #include "thread.h" #define MAX 10000 LRESULT CALLBACK WindowFunc(HWND, UINT, WPARAM, LPARAM); DWORD WINAPI

MyThread1(LPVOID param); char szWinName[] = "MyWin"; /* name of window class / char str[255]; /* holds output strings / DWORD Tid1; /* thread IDs / HANDLE hWaitTimer; /* handle to semaphore / int WINAPI WinMain(HINSTANCE hThisInst, HINSTANCE hPrevInst, LPSTR lpszArgs, int nWinMode) { 32. oldal Többszálú programozás Windows NT alatt HWND hwnd; MSG msg; WNDCLASSEX wcl; HANDLE hAccel; /* Define a window class. */ wcl.cbSize = sizeof(WNDCLASSEX); wcl.hInstance = hThisInst; /* handle to this instance / wcl.lpszClassName = szWinName; /* window class name / wcl.lpfnWndProc = WindowFunc; /* window function / wcl.style = 0; /* default wcl / wcl.hIcon = LoadIcon(NULL, IDI APPLICATION); wcl.hIconSm = LoadIcon(NULL, IDI APPLICATION); wcl.hCursor = LoadCursor(NULL, IDC ARROW); /* specify name of menu resource / wcl.lpszMenuName = "WaitTimerMenu"; /* main menu / wcl.cbClsExtra = 0; /* no extra / wcl.cbWndExtra = 0; /* information needed / /*

Make the window white. */ wcl.hbrBackground = GetStockObject(WHITE BRUSH); /* Register the window class. */ if(!RegisterClassEx(&wcl)) return 0; /* Now that a window class has been registered, a window can be created. */ hwnd = CreateWindow( szWinName, /* name of window class / "Use a Waitable Timer", /* title / WS OVERLAPPEDWINDOW, /* window style - normal / CW USEDEFAULT, /* X coordinate - let Windows decide / CW USEDEFAULT, /* Y coordinate - let Windows decide / CW USEDEFAULT, /* width - let Windows decide / CW USEDEFAULT, /* height - let Windows decide / HWND DESKTOP, /* no parent window / NULL, /* no override of class menu / hThisInst, /* handle of this instance of the program / NULL /* no additional arguments / ); /* load accelerators / hAccel = LoadAccelerators(hThisInst, "WaitTimerMenu"); /* Display the window. */ ShowWindow(hwnd, nWinMode); UpdateWindow(hwnd); /* Create the message loop. */ while(GetMessage(&msg, NULL, 0,

0)) { if(!TranslateAccelerator(hwnd, hAccel, &msg)) { TranslateMessage(&msg); /* translate keyboard messages / DispatchMessage(&msg); /* return control to Windows NT / } 33. oldal Többszálú programozás Windows NT alatt } return msg.wParam; } /* This function is called by Windows NT and is passed messages from the message queue. */ LRESULT CALLBACK WindowFunc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { int response; switch(message) { case WM CREATE: /* create the timer / hWaitTimer = CreateWaitableTimer(NULL, 1, NULL); break; case WM COMMAND: switch(LOWORD(wParam)) { case IDM THREAD: CreateThread(NULL, 0, (LPTHREAD START ROUTINE)MyThread1, (LPVOID) hwnd, 0, &Tid1); break; case IDM EXIT: response = MessageBox(hwnd, "Quit the Program?", "Exit", MB YESNO); if(response == IDYES) PostQuitMessage(0); break; case IDM HELP: MessageBox(hwnd, "F1: Help F2: Demonstrate Timer", "Help", MB OK); break; } break; case

WM DESTROY: /* terminate the program / PostQuitMessage(0); break; default: /* Let Windows NT process any messages not specified in the preceding switch statement. */ return DefWindowProc(hwnd, message, wParam, lParam); } return 0; } /* Demonstrate waitable timer. */ DWORD WINAPI MyThread1(LPVOID param) { int i; HDC hdc; SYSTEMTIME systime; FILETIME filetime; LARGE INTEGER li; /* add 10 seconds onto current system time / GetSystemTime(&systime); SystemTimeToFileTime(&systime, &filetime); li.LowPart = filetimedwLowDateTime; 34. oldal Többszálú programozás Windows NT alatt li.HighPart = filetimedwHighDateTime; li.QuadPart += 100000000L; /* set the timer / SetWaitableTimer(hWaitTimer, &li, 0, NULL, NULL, 0); /* minimize the window until the timer expires / ShowWindow((HWND) param, SW MINIMIZE); /* wait for timer / if(WaitForSingleObject(hWaitTimer, 100000)==WAIT TIMEOUT) { MessageBox((HWND)param, "Time Out Thread 1", "Timer Error", MB OK);

return 0; } /* beep and restore window / MessageBeep(MB OK); ShowWindow((HWND) param, SW RESTORE); hdc = GetDC((HWND) param); for(i=0; i<MAX; i++) { wsprintf(str, "Thread 1: loop # %5d ", i); TextOut(hdc, 1, 1, str, lstrlen(str)); } ReleaseDC((HWND) param, hdc); return 0; } Ez a program a korábban már leírt THREAD.H header fájlt használja Tovább szükség van az alábbi erőforrás fájlra is: #include <windows.h> #include "thread.h" WaitTimerMenu MENU { POPUP "&Options" { MENUITEM "&Waitable Timer F2", IDM THREAD MENUITEM "E&xit F10", IDM EXIT } MENUITEM "&Help", IDM HELP } WaitTimerMenu ACCELERATORS { VK F2, IDM THREAD, VIRTKEY VK F10, IDM EXIT, VIRTKEY VK F1, IDM HELP, VIRTKEY } Waitable timer használata A waitable timer számos igen érdekes lehetőséget nyújt. Például készíthető vele számítógépes ébresztőóra Ehhez készítsünk egy dialógus ablakot, amelyben a

felhasználó megadhatja az ébresztési időt, majd ezzel az 35. oldal Többszálú programozás Windows NT alatt idővel inicializáljuk a waitable timert. Waitable timerrel könnyen készíthető automatikus mentés vagy fájlmásoló alkalmazás. Bár ilyen típusú programokat normál timer és más szinkronizáló objektumok kombinációjával is készíthetünk, a waitable timer használata nagyban leegyszerűsíti ezt a folyamatot. Új taszk készítése Bár a Windows NT szál-alapú multitaszkolási lehetőségei igen nagymértékben befolyásolhatják programjainkat, még van lehetőség folyamat-alapú multitaszkolásra is. Folyamat-alapú párhuzamosítás esetén az egyik folyamat nem egyszerűen egy szálat hoz létre önmagán belül, hanem elindít egy másik programot. Ez Windows NT alatt a CreateProcess() API függvénnyel tehető meg, melynek prototípusa a következő: BOOL CreateProcess(LPCSTR lpszName, LPSTR lpszComLine, LPSECURITY ATTRIBUTES lpProcAttr,

LPSECURITY ATTRIBUTES lpThreadAttr, BOOL InheritAttr, DWORD How, LPVOID lpEnv, LPSTR lpszDir, LPSTARTUPINFO lpStartInfo, LPPROCESS INFORMATION lpPInfo); Az lpszName tartalmazza az elindítandó program nevét, és esetleg a teljes elérési utat is. A program által igényelt parancssor paraméterek az lpszComLine változó által mutatott stringben adhatók meg. Ha lpszName értéke NULL, akkor a függvény az lpszComLine stringjének első szavát fogja a program neveként értelmezni. Éppen ezért tipikus megoldás, hogy az lpszName változó NULL, a program neve és a parancssor változók pedig az lpszComLine paraméterben kapnak helyet. Ha 16-bites folyamatot indítunk, akkor lpszName-nek NULL-nak kell lennie. Az lpProcAttr és az lpThreadAttr paraméterekkel a folyamathoz tartozó biztonsági attribútumok adhatók meg. Ezek lehet NULL értékűek is, ekkor a függvény az alapértelmezett security descriptort használja. Ha az InheritAttr nem-nulla, akkor az új folyamat

örökli a hívó folyamat által használt leírókat, ha ez a paraméter nulla, akkor a leírók nem öröklődnek. Alapértelmezésben az új folyamat "normálisan" fut. A How paraméterben viszont megadhatók olyan értékek, amelyek az új folyamat futását befolyásolhatják. (Például egyedi prioritás adható meg, vagy debug jelzés stb) Az lpEnv változó egy bufferre mutat, amelyik az új folyamat környezeti paramétereit tartalmazza. Ha ez NULL, akkor az új folyamat örökli a hívó folyamat környezetét. Az új folyamatot tartalmazó meghajtó és könyvtár neve megadható az lpszDir változóban. Ha NULL, akkor a hívó folyamat meghajtóját és könyvtárát használja a függvény. Az lpStartInfo egy STARTUPINFO struktúrára mutató pointer, amely meghatározza, hogy az új folyamat fő ablaka hogyan nézzen ki. A STARTUPINFO definíciója a következő: typedef struct STARTUPINFO { DWORD cb; // a STARTUPINFO struktúra mérete LPTSTR lpReserved;

// NULL kell LPTSTR lpDesktop; // a desktop neve LPTSTR lpTitle; // a konzolablak címe (csak konzolnál) DWORD dwX; // az új ablak bal- DWORD dwY; // DWORD dwXSize; // az új ablak vízszintes mérete DWORD dwYSize; // az új ablak függőleges mérete -felső sarkának koordinátái 36. oldal Többszálú programozás Windows NT alatt DWORD dwXCountChars; // konzol buffer méret DWORD dwYCountChars; // konzol buffer méret DWORD dwFillAttribute; // kezdeti szöveg és háttérszín DWORD dwFlags; // az aktív mezőket határozza meg WORD wShowWindow; // hogyan jelenjen meg az ablak pl. SW SHOW stb WORD cbReserved2; // nulla kell LPBYTE lpReserved2; // NULL kell HANDLE hStdInput; // standard leírók HANDLE hStdOutput; HANDLE hStdError; } STARTUPINFO; A dwX, dwY, dwXSize, dwYSize, dwXCountChars, dwYCountChars, dwFillAttribute és wShowWindow értékeket a függvény figyelmen kívül hagyja, ha a dwFlags mezőben megfelelő értékekkel nem

kapcsoljuk be őket. A dwFlags értékei a következők: Makró Mely mezőket kapcsolja be STARTF USESHOWWINDOW wShowWindow STARTF USESIZE dwXSize és dwYSize STARTF USEPOSITION dwX és dwY STARTF USECOUNTCHARS dwXCountChars és dwYCountChars STARTF USEFILLATRIBUTE dwFillAttribute STARTF USESTDHANDLES hStdInput, hStdOutput és hStdError A dwFlags továbbá tartalmazhatja ezeket az értékeket is: STARTF FORCEONFEEDBACK A feedback kurzort bekapcsolja. STARTF FORCEOFFFEEDBACK A feedback kurzort kikapcsolja. Megjegyzés: Az lpTitle, dwXCountChars és dwYCountChars mezőket a függvény csak konzol alkalmazások esetén használja. Általában nincs szükség a STARTUPINFO összes mezőjére, hanem a többségét figyelmen kívül lehet hagyni. Legtöbbször elég a cb változóban megadni a struktúra méretét, és a többi mezőt pedig NULL-ra állítani. A CreateProcess() utolsó paramétere az lpPInfo, amelyik egy PROCESS INFORMATION struktúrára mutat: typedef struct

PROCESS INFORMATION { HANDLE hProcess; // az új folyamathoz tartozó leíró HANDLE hThread; // a fő szálhoz tartozó leíró DWORD dwProcessId; // az új folyamat azonosítója DWORD dwThreadId; // az új szál azonosítója } PROCESS INFORMATION; Az új folyamatnak és a fő szálnak a leíróját a hívó a hProcess és a hThread változóban, az azonosítóit pedig a dwProcessId és a dwThreadId változóban kapja vissza. A CreateProcess() sikeres hívás esetén nem-nullával, hiba esetén pedig nullával tér vissza. A következő kódrészlet a CreateProcess() használatát mutatja be: STARTUPINFO startinfo; PROCESS INFORMATION pinfo; 37. oldal Többszálú programozás Windows NT alatt /* . */ startinfo.cb = sizeof(STARTUPINFO); startinfo.lpReserved = NULL; startinfo.lpDesktop = NULL; startinfo.lpTitle = NULL; startinfo.dwFlags = STARTF USESHOWWINDOW; startinfo.cbReserved2 = 0; startinfo.lpReserved2 = NULL; startinfo.wShowWindow = SW SHOW;

CreateProcess(NULL, "test.exe", NULL, NULL, 0, 0, NULL, NULL, &startinfo, &pinfo); Ez a kód elindítja a TEST.EXE nevű programot, amelynek a szülő folyamat könyvtárában kell lennie Miután létrehoztuk, az új folyamat nagyrészt független az őt létrehozó folyamattól. Természetesen a TerminateProcess() API függvénnyel a szülő folyamat bezárhatja a gyermek folyamatot. Hordozhatóság: A Windows 3.1 nem támogatja a CreateProcess() függvényt, hanem e helyett a WinExec() függvényt kell használni. A WinExec() azonban elavult, ezért Win32 programok nem használhatják Régebbi programoknál átírásánál a WinExec() hívásokat CreateProcess() hívásokra kell cserélni. 38. oldal