Threads erstellen
Auf der Windows-Plattform bietet die Windows-API Unterstützung für Multithreading. Weiter oben, bei den Konzepten von Prozessen und Threads, haben wir erwähnt, dass ein Programm mindestens einen Thread hat, der als Hauptthread bezeichnet wird. Wenn wir nicht explizit Threads erstellen, ist das von uns erzeugte Programm ein Single-Thread-Programm mit nur dem Hauptthread.
Im Folgenden betrachten wir die threadbezogenen Operationen und Methoden in Windows:
CreateThread und CloseHandle
CreateThread wird verwendet, um einen Thread zu erstellen. Der Funktionsprototyp sieht wie folgt aus:
HANDLE WINAPI CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes, //线程安全相关的属性,常置为NULL
SIZE_T dwStackSize, //新线程的初始化栈在大小,可设置为0
LPTHREAD_START_ROUTINE lpStartAddress, //被线程执行的回调函数,也称为线程函数
LPVOID lpParameter, //传入线程函数的参数,不需传递参数时为NULL
DWORD dwCreationFlags, //控制线程创建的标志
LPDWORD lpThreadId //传出参数,用于获得线程ID,如果为NULL则不返回线程ID
);
Beschreibung:
lpThreadAttributes: Ein Zeiger auf eine SECURITY_ATTRIBUTES-Struktur, die bestimmt, ob das zurückgegebene Handle von untergeordneten Prozessen geerbt werden kann. Wenn er NULL ist, kann das zurückgegebene Handle nicht von untergeordneten Prozessen geerbt werden.
- dwStackSize: Die Anfangsgröße des Thread-Stacks in Bytes. Das System weist diesen Wert zu...
lpStartAddress: Ein Zeiger auf einen Funktionszeiger; diese Funktion wird vom Thread aufgerufen und ausgeführt. Daher wird diese Funktion auch Threadfunktion (ThreadProc) genannt, sie ist die Startadresse der Threadausführung. Die Threadfunktion ist eine Callback-Funktion, die vom Betriebssystem im Thread aufgerufen wird. Der Prototyp der Threadfunktion sieht wie folgt aus:
DWORD WINAPI ThreadProc(LPVOID lpParameter); //lpParameter是传入的参数,是一个空指针
lpParameter: Der an die Threadfunktion (ThreadProc) übergebene Parameter; verwenden Sie NULL, wenn kein Parameter übergeben werden muss.
dwCreationFlags: Flags, die die Threaderstellung steuern. Es gibt drei Typen: 0: Der Thread wird nach der Erstellung sofort ausgeführt; CREATE_SUSPENDED: Der Thread wechselt nach der Erstellung in den Bereitschaftszustand und wird erst aufgerufen, wenn der Thread fortgesetzt wird; STACK_SIZE_PARAM_IS_A_RESERVATION: Der Parameter dwStackSize gibt die anfängliche Stackgröße des Threads an. Wenn das Flag STACK_SIZE_PARAM_IS_A_RESERVATION nicht angegeben ist, wird dwStackSize auf einen vom System reservierten Wert gesetzt.
Rückgabewert: Wenn der Thread erfolgreich erstellt wurde, wird das Handle des neuen Threads zurückgegeben; andernfalls wird NULL zurückgegeben. Wenn die Thread-Erstellung fehlschlägt, können Fehlerinformationen über die GetLastError-Funktion abgerufen werden.
BOOL WINAPI CloseHandle(HANDLE hObject); //关闭一个被打开的对象句柄
Diese Funktion kann verwendet werden, um das erstellte Thread-Handle zu schließen. Wenn die Funktion erfolgreich ausgeführt wird, gibt sie true (ungleich null) zurück; wenn sie fehlschlägt, gibt sie false (0) zurück. Wenn die Ausführung fehlschlägt, können Sie die GetLastError-Funktion aufrufen, um Fehlerinformationen zu erhalten.
[Demo1]: Den einfachsten Thread erstellen
Beispiel
Das Ergebnis ist wie folgt:
主线程:i = 0 子线程:i = 0 主线程:i = 1 子线程:i = 1 子线程:i = 2 主线程:i = 2 子线程:i = 3 主线程:i = 3 子线程:i = 4 主线程:i = 4
[Demo2]: Parameter an die Thread-Funktion übergeben
Beispiel
Ergebnis:
主线程 === 线程1 — 0 0 线程2 — 0 线程1 — 1 主线程 === 1 线程2 — 1 主线程 === 2 线程1 — 2 线程2 — 2 主线程 === 3 线程2 — 3 线程1 — 3 主线程 === 4 线程2 — 4 线程1 — 4 线程2 — 5 请按任意键继续… 线程2 — 6 线程2 — 7 线程2 — 8 线程2 — 9
CreateMutex、WaitForSingleObject、ReleaseMutex
Anhand von [Demo2] können wir sehen, dass, obwohl die erstellten Child-Threads alle normal ausgeführt wurden, das Ausgabeergebnis nicht unseren Erwartungen entsprach. Wir erwarteten, dass auf jede Ausgabeanweisung ein Zeilenumbruch folgt, aber die Ergebnisse waren nicht alle so. Das liegt daran, dass während der Thread-Ausführung keine Synchronisierung durchgeführt wurde. In der ersten Ausgabezeile beispielsweise, nachdem der Hauptthread "main thread ===" ausgegeben hat, ist seine Zeitscheibe aufgebraucht; dann ist Child-Thread 1 an der Reihe auszugeben. Nachdem Child-Thread 1 "thread 1 —" ausgegeben hat, ist seine Zeitscheibe ebenfalls aufgebraucht; dann ist der Hauptthread an der Reihe, "0" auszugeben, und danach gibt Child-Thread 1 "0" aus. So entsteht das Ergebnis "main thread === thread 1 — 0 0".
Hauptthread: cout << "main thread === " << i << endl;
Child-Thread: cout << pThreadData->strThreadName << " — " << i << endl;
Um diese Situation zu vermeiden, führen wir eine einfache Synchronisierung der Threads durch. Hier verwenden wir einen Mutex.
Ein Mutex ist einem binären Semaphor ähnlich, bei dem auf eine Ressource nur von einem Thread zugegriffen werden darf. Anders als bei einem binären Semaphor – bei dem ein Semaphor von jedem Thread im gesamten System erworben und freigegeben werden kann, das heißt, dasselbe Semaphor kann von einem Thread erworben und von einem anderen freigegeben werden – verlangt ein Mutex, dass der Thread, der die Mutex-Sperre erworben hat, auch derjenige sein muss, der sie freigibt. Es ist ungültig, wenn andere Threads den Mutex in seinem Namen freigeben.
Bei der Verwendung von Mutexen zur Thread-Synchronisierung werden die folgenden Funktionen verwendet:
HANDLE WINAPI CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes, //线程安全相关的属性,常置为NULL
BOOL bInitialOwner, //创建Mutex时的当前线程是否拥有Mutex的所有权
LPCTSTR lpName //Mutex的名称
);
Beschreibung:lpMutexAttributes ist ebenfalls eine Sicherheitsstruktur mit derselben Funktion wie lpThreadAttributes in CreateThread und gibt an, ob das zurückgegebene Handle von untergeordneten Prozessen geerbt werden kann. Wenn NULL, kann das zurückgegebene Handle nicht von untergeordneten Prozessen geerbt werden. bInitialOwner gibt an, ob der aktuelle Thread den Mutex beim Erstellen besitzt. Wenn TRUE, wird der aktuell erstellende Thread als Besitzer des Mutex-Objekts festgelegt, und andere Threads müssen ReleaseMutex aufrufen, bevor sie darauf zugreifen. lpName ist der Name des Mutex.
DWORD WINAPI WaitForSingleObject(
HANDLE hHandle, //要获取的锁的句柄
DWORD dwMilliseconds //超时间隔
);
Beschreibung:WaitForSingleObject wartet auf ein angegebenes Objekt (z. B. ein Mutex-Objekt), bis das Objekt sich in einem nicht belegten Zustand befindet (z. B. wenn das Mutex-Objekt freigegeben wurde) oder das festgelegte Zeitintervall abgelaufen ist. Außerdem gibt es eine ähnliche Funktion, WaitForMultipleObjects, die auf ein oder alle angegebenen Objekte wartet, bis alle Objekte sich in einem nicht belegten Zustand befinden oder bis das festgelegte Zeitintervall abgelaufen ist.
hHandleDas Handle des angegebenen Objekts, auf das gewartet werden soll. dwMilliseconds: Das Timeout-Intervall in Millisekunden; wenn dwMilliseconds ungleich null ist, wird gewartet, bis das dwMilliseconds-Intervall abgelaufen ist oder das Objekt nicht belegt ist. Wenn dwMilliseconds INFINITE ist, bedeutet dies unbegrenztes Warten, bis das Objekt, auf das gewartet wird, sich in einem nicht belegten Zustand befindet.
BOOL WINAPI ReleaseMutex(HANDLE hMutex);
Beschreibung:Gibt das im Besitz befindliche Mutex-Sperrobjekt frei. hMutex ist das Handle des freizugebenden Mutex.
[Demo3]: Thread-Synchronisierung
Beispiel
Ergebnis:
主线程 === 0 线程1 — 0 线程2 — 0 主线程 === 1 线程1 — 1 线程2 — 1 主线程 === 2 线程1 — 2 线程2 — 2 主线程 === 3 线程1 — 3 线程2 — 3 主线程 === 4 线程1 — 4 请按任意键继续… 线程2 — 4 线程2 — 5 线程2 — 6 线程2 — 7 线程2 — 8 线程2 — 9
Um die Bedeutung der Thread-Synchronisierung und die Verwendung von Mutexen besser zu verstehen, sehen wir uns ein weiteres Beispiel an.
Der Kauf von Zugtickets ist das, was allen bei der Heimreise zum Frühlingsfest am wichtigsten ist. Lassen Sie uns einfach ein Zugticket-Verkaufssystem simulieren (um das Programm einfach zu halten, extrahieren wir das einfachste Modell für die Simulation): Es gibt 500 Zugtickets von Peking nach Ganzhou, die gleichzeitig an 8 Schaltern verkauft werden, wodurch Systemstabilität und Datenatomarität gewährleistet werden.
[Demo4]: Simulation eines Zugticket-Verkaufssystems
SaleTickets.h
SaleTickets.cpp
Testprogramm:
//售票系统
void Test2()
{
//创建一个互斥量
g_hMutex = CreateMutex(NULL, FALSE, NULL);
//初始化火车票
TICKET ticket;
ticket.nCount = 100;
strcpy(ticket.strTicketName, "北京-->赣州");
const int THREAD_NUMM = 8;
THD_DATA threadSale[THREAD_NUMM];
HANDLE hThread[THREAD_NUMM];
for(int i = 0; i < THREAD_NUMM; ++ i)
{
threadSale[i].pTicket = &ticket;
string strThreadName = convertToString(i);
strThreadName = "窗口" + strThreadName;
strcpy(threadSale[i].strThreadName, strThreadName.c_str());
//创建线程
hThread[i] = CreateThread(NULL, NULL, SaleTicket, &threadSale[i], 0, NULL);
//请求获得一个互斥量锁
WaitForSingleObject(g_hMutex, INFINITE);
cout << threadSale[i].strThreadName << "开始出售 " << threadSale[i].pTicket->strTicketName << " 的票..." << endl;
//释放互斥量锁
ReleaseMutex(g_hMutex);
//关闭线程
CloseHandle(hThread[i]);
}
system("pause");
}
Ergebnis:
窗口0开始出售 北京–>赣州 的票… 窗口0出售第100的票,出票成功!剩余99张票. 窗口1开始出售 北京–>赣州 的票… 窗口1出售第99的票,出票成功!剩余98张票. 窗口0出售第98的票,出票成功!剩余97张票. 窗口2开始出售 北京–>赣州 的票… 窗口2出售第97的票,出票成功!剩余96张票. 窗口1出售第96的票,出票成功!剩余95张票. 窗口0出售第95的票,出票成功!剩余94张票. 窗口3开始出售 北京–>赣州 的票… 窗口3出售第94的票,出票成功!剩余93张票. 窗口2出售第93的票,出票成功!剩余92张票. 窗口1出售第92的票,出票成功!剩余91张票. 窗口0出售第91的票,出票成功!剩余90张票. 窗口4开始出售 北京–>赣州 的票… 窗口4出售第90的票,出票成功!剩余89张票. 窗口3出售第89的票,出票成功!剩余88张票. 窗口2出售第88的票,出票成功!剩余87张票. 窗口1出售第87的票,出票成功!剩余86张票. 窗口0出售第86的票,出票成功!剩余85张票. 窗口5开始出售 北京–>赣州 的票… 窗口5出售第85的票,出票成功!剩余84张票. 窗口4出售第84的票,出票成功!剩余83张票. 窗口3出售第83的票,出票成功!剩余82张票. 窗口2出售第82的票,出票成功!剩余81张票.
Quelle: http://blog.csdn.net/luoweifu/article/details/46835437