Event |
A worker thread can be temporarily suspended because it does not have any work to do. The main thread can use an event to wake up any worker thread (that has been suspended) so that the worker thread can complete some pending work. When a worker thread is not longer necessary, an event can be used to wake up the worker thread so that it can terminate (or kill) itself. Una thread de trabajo puede estar temporalmente pausada porque ésta no tiene trabajo por hacer. La thread principal puede usar un evento para despertar cualquier thread (que esta pausada) de tal forma que la thread de trabajo pueda completar algún trabajo pendiente. Cuando una thread de trabajo ya no es necesaria, un evento puede ser usado para despertar la thread de trabajo de tal forma que esta pueda terminar (o matarse así misma). |
Tip |
The main advantage of suspend a thread instead of terminating it is that it is not necessary to create a new thread every time it is needed. An event is necessary to wake up threads that are suspended. La principal ventaja de pausar una thread en lugar de terminarla es que no es necesario crear una nueva thread cada vez que es necesaria. Un evento es necesario para despertar una thread que esta pausada. |
::WaitForSingleObject and ::WaitForMultipleObjects |
Both functions can be used to suspend a thread until an object (in the following example an event) is signaled. These functions are used to wait for completion of results by other threads without wasting CPU time. These functions may return one of the following values: WAIT_ABANDONED, WAIT_OBJECT_0, WAIT_TIMEOUT and WAIT_FAILED. Ambas funciones pueden ser usadas para suspender una thread hasta que un objeto (en el siguiente ejemplo un evento) es señalizado. Estas funciones son usadas para esperar por el completado de resultados por otras threads sin gastar tiempo de CPU. Estas funciones pueden regresar uno de los siguientes valores: WAIT_ABANDONED, WAIT_OBJECT_0, WAIT_TIMEOUT and WAIT_FAILED. |
Problem 1 |
Search over the Internet about the meaning of each of the following values: WAIT_ABANDONED, WAIT_OBJECT_0, WAIT_TIMEOUT and WAIT_FAILED. Busque en la Internet sobre el significado de cada uno de los siguientes valores: WAIT_ABANDONED, WAIT_OBJECT_0, WAIT_TIMEOUT y WAIT_FAILED. |
Wait forever |
In some cases, it is necessary to wait until another thread completes its work. The code below shows how you can use ::WaitForSingleObject to wait until the worker thread has completed the execution of the FunctionB. You MUST not call ::WaitForSingleObject on the main thread in a Window application because the program GUI will stop working. In the code below, ::WaitForSingleObject is called from FunctionA. En algunos casos, es necesario esperar hasta que otra thread termine su trabajo. El código de abajo ilustra cómo usar ::WaitForSingleObject para esperar hasta que la thread trabajadora complete la ejecución de FunctionB. Usted no DEBE llamar ::WaitForSingleObject en la thread principal en una aplicación de ventana porque la GUI del programa dejará de responder. En el código de abajo, ::WaitForSingleObject es llamado desde FunctionA. |
Program.h |
#pragma once //______________________________________ Program.h #include "resource.h" class Program: public Win::Dialog { public: Program() { hA = NULL; } ~Program() { } HANDLE hA; static unsigned WINAPI FunctionA(LPVOID param); static unsigned WINAPI FunctionB(LPVOID param); . . . }; |
Program.cpp |
. . . void Program::Window_Open(Win::Event& e) { //______________________________________ 1. Execute FunctionA in worker thread hA = (HANDLE)::_beginthreadex(NULL, 0, FunctionA, (LPVOID)*this, 0, NULL); } unsigned WINAPI Program::FunctionA(LPVOID param) { Program* p = (Program*)param; //______________________________________ 1. Run FunctionB in worker thread HANDLE hB = (HANDLE)::_beginthreadex(NULL, 0, FunctionB, (LPVOID)p, 0, NULL); // Do other computations . . . //______________________________________ 2. Wait for thread to complete ::WaitForSingleObject(hB, INFINITY); ::CloseHandle(hB); //______________________________________ 3. Notify main thread ::PostMessage(p->hWnd, WM_APP, 0, 0); return 0; } void Program::Window_App(Win::Event& e) { ::CloseHandle(hA); } unsigned WINAPI Program::FunctionB(LPVOID param) { return 0; } |
Wait and loop |
In other cases, you can wait at time intervals. If the call ::WaitForSingleObject, times out, you can perform some operations in your program, and later, call again ::WaitForSingleObject. The code below calls ::WaitForSingleObject and wait for two seconds for the FunctionB to complete. If FunctionB does not complete within two seconds, FunctionA will call again ::WaitForSingleObject. Observe that every time ::WaitForSingleObject times out, a WM_APP message is sent to the main thread to show an additional asterisk in the title bar of the window to pacify the user. En otros casos, usted puede esperar a intervalos de tiempo. Si la llamada a ::WaitForSingleObject, regresa WAIT_TIMEOUT, usted puede hacer algunos operaciones en su programa, y después, llamar otra vez ::WaitForSingleObject. El código de abajo llama ::WaitForSingleObject y espera durante dos segundos para que FunctionB complete. Si FunctionB no completa dentro de dos segundos, FunctionA llamará otra vez ::WaitForSingleObject. Observe que cada vez que ::WaitForSingleObject regresa WAIT_TIMEOUT, un mensaje de WM_APP es enviado a la thread principal para mostrar un asterisco adicional en la barra de título de la ventana para tranquilizar al usuario. |
Program.h |
#pragma once //______________________________________ Program.h #include "resource.h" class Program: public Win::Dialog { public: Program() { nA = NULL; } ~Program() { } HANDLE hA; static unsigned WINAPI FunctionA(LPVOID param); static unsigned WINAPI FunctionB(LPVOID param); . . . }; |
Program.cpp |
. . . void Program::Window_Open(Win::Event& e) { //______________________________________ 1. Execute FunctionA in worker thread hA = (HANDLE)::_beginthreadex(NULL, 0, FunctionA, (LPVOID)*this, 0, NULL); } unsigned WINAPI Program::FunctionA(LPVOID param) { Program* p = (Program*)param; //______________________________________ 1. Run FunctionB in worker thread HANDLE hB = (HANDLE)::_beginthreadex(NULL, 0, FunctionB, (LPVOID)p, 0, NULL); // Do other computations . . . //______________________________________ 2. Wait for thread to complete while (::WaitForSingleObject(hB, 2000) == WAIT_TIMEOUT) //Every two seconds { ::PostMessage(p->hWnd, WM_APP, 0, 0); // cout<<"*"; // Pacifier for a console application } ::CloseHandle(hB); //______________________________________ 3. Notify main thread ::PostMessage(p->hWnd, WM_APP+1, 0, 0); return 0; } unsigned WINAPI Program::FunctionB(LPVOID param) { return 0; } void Program::Window_App(Win::Event& e) { this->Text += L"*"; // Pacifier } void Program::Window_App1(Win::Event& e) { ::CloseHandle(hA); } |
Problem 2 |
Create a Wintempla Dialog application called PiRecycle to estimate the value of pi using a series of Taylor. Edit the GUI as shown using Wintempla. Before closing Wintempla, double click anywhere in the GUI to open the Properties Dialog of the main dialog, in the Event tab: check the App event (A custom application event) and Close (When the window should terminate) Cree una aplicación de Dialogo usando Wintempla llamada PiRecycle para estimar el valor de pi usando una serie de Taylor. Edite la interface gráfica como se muestra usando Wintempla. Antes de cerrar Wintempla, hace doble clic en cualquier parte en la GUI para abrir el Diálogo de Propiedades del diálogo principal, en la Pestaña de Eventos: marque el evento App (A custom application event) y Close (When the window should terminate). |
PiRecycle.h |
#pragma once //______________________________________ PiRecycle.h #include "Resource.h" #define WORK_ID 100 class PiRecycle: public Win::Dialog { public: PiRecycle() { eventRequest = ::CreateEvent(NULL, FALSE, FALSE, NULL); eventCompletion = ::CreateEvent(NULL, FALSE, FALSE, NULL); ::InitializeCriticalSection(&cs); } ~PiRecycle() { if (eventRequest) ::CloseHandle(eventRequest); if (eventCompletion) ::CloseHandle(eventCompletion); ::DeleteCriticalSection(&cs); } CRITICAL_SECTION cs; bool terminate = false; HANDLE eventRequest; HANDLE eventCompletion; static unsigned WINAPI ComputePi(LPVOID param); HANDLE hThread; // size_t termCount = 0; double estimatePi = 0.0; . . . }; |
PiRecycle.cpp |
. . . void PiRecycle::Window_Open(Win::Event& e) { tbxTermCount.Text = L"1000000000"; hThread = (HANDLE)::_beginthreadex(NULL, 0, ComputePi, (LPVOID)this, 0, NULL); } void PiRecycle::btRun_Click(Win::Event& e) { tbxResult.Text = L" * * *"; btRun.Enabled = false; termCount = tbxTermCount.IntValue; ::SetEvent(eventRequest); // Wake up the thread so that it can compute the value of PI } void PiRecycle::Window_Close(Win::Event& e) { ::EnterCriticalSection(&cs); terminate = true; ::LeaveCriticalSection(&cs); // ::SetEvent(eventRequest); // Wake up the thread so that it can terminate itself ::WaitForSingleObject(hThread, INFINITE); ::CloseHandle(hThread); this->Destroy(); // Use this to close and destroy the Window } unsigned WINAPI PiRecycle::ComputePi(LPVOID param) { PiRecycle* pir = (PiRecycle*)param; while(true) { ::WaitForSingleObject(pir->eventRequest, INFINITE);// Suspend the thread // ::EnterCriticalSection(&pir->cs); if (pir->terminate == true) { ::LeaveCriticalSection(&pir->cs); break; // Terminate the thread cleanly } ::LeaveCriticalSection(&pir->cs); //________________________________________________________________ Compute PI double sum = 0.0; for (size_t i =0; i<pir->termCount; i++) { if (i%2 == 0) sum += (1.0/(2*i+1)); else sum -= (1.0/(2*i+1)); } pir->estimatePi = 4.0*sum; ::PostMessage(pir->hWnd, WM_APP, (WPARAM)0, (LPARAM)WORK_ID); // Notify the main thread that we are done! ::SetEvent(pir->eventCompletion); } return 0; } void PiRecycle::Window_App(Win::Event& e) { if (e.lParam == WORK_ID) { ::WaitForSingleObject(eventCompletion, INFINITE); btRun.Enabled = true; //______________________________________________ Display result wchar_t text[256]; _snwprintf_s(text, 256, _TRUNCATE, L"%.15f", estimatePi); tbxResult.Text = text; } } |
Tip |
Wintempla provides the Mt::SuspendedThread class to simplify the use of suspended threads. Always try creating classes to simplify your code. Remember that a class must be as generic as possible so that it can be used in other applications. Wintempla provides some classes to simplify the use of events, critical sections and safe-thread values. Wintempla proporciona la clase Mt::SuspendedThread para simplificar el uso de threads suspendidas. Siempre trate de crear clases para simplificar su código. Recuerde que una clase debe ser tan genérica como se pueda de tal forma que ésta puede usarse en otras aplicaciones. Wintempla proporciona algunas clases para simplificar el uso de evento, secciones críticas y valores seguros en threads. |
Problem 3 |
Create a project called PiRecycleX to simplify the previous program using Wintempla classes. Note that the Mt::SuspendedThread class can help you design your own classes for multithread programs. Edit the GUI as shown using Wintempla. Before closing Wintempla, double click anywhere in the GUI to open the Properties Dialog of the main dialog, in the Event tab: check the App event (A custom application event), Timer and Close (When the window should terminate) Cree un proyecto llamado PiRecycleX para simplificar el programa anterior usando las clases de Wintempla. Observe que la clase Mt::SuspendedThread puede ayudarlo a diseñar sus propias clases para programas multihilo. Edite la interface gráfica como se muestra usando Wintempla. Antes de cerrar Wintempla, hace doble clic en cualquier parte en la GUI para abrir el Diálogo de Propiedades del diálogo principal, en la Pestaña de Eventos: marque el evento App (A custom application event), Timer y Close (When the window should terminate). |
PiRecycleX.h |
#pragma once //______________________________________ PiRecycleX.h #include "Resource.h" #define WORK_ID 100 class PiRecycleX: public Win::Dialog, public Mt::IThreadX { public: PiRecycleX() { } ~PiRecycleX() { } Mt::DoubleTs progress; Sys::LowResStopwatch stopwatch; // Mt::SuspendedThread suspendedThread; size_t termCount = 0; double estimatePi = 0.0; //__________________________________ Mt::IThreadX DWORD ThreadFunc(Mt::BoolTs& cancel, size_t threadIndex, size_t numThreads); . . . }; |
PiRecycleX.cpp |
. . . void PiRecycleX::Window_Open(Win::Event& e) { tbxTermCount.Text = L"1000000000"; } void PiRecycleX::btRun_Click(Win::Event& e) { this->timer.Set(1, 1000); stopwatch.Start(); tbxResult.Text = L" * * *"; btRun.Enabled = false; termCount = tbxTermCount.IntValue; suspendedThread.WakeUp(*this); } DWORD PiRecycleX::ThreadFunc(Mt::BoolTs& cancel, size_t threadIndex, size_t numThreads) { double sum = 0.0; for (size_t i = 0, j = 0; i < termCount; i++, j++) { //___________________________________________ 1. Compute pi if (i%2 == 0) sum += (1.0/(2*i+1)); else sum -= (1.0/(2*i+1)); //___________________________________________ 2. Thread communication if (j == 10000) { j = 0; if (cancel == true) break; progress = (100.0*i)/termCount; } } estimatePi = 4.0*sum; ::PostMessage(hWnd, WM_APP, (WPARAM)0, (LPARAM)WORK_ID); // Notify the main thread that we are done! return 0; } void PiRecycleX::Window_Close(Win::Event& e) { if (suspendedThread.IsBusy()) { this->MessageBox(L"Program is busy", L"PiRecycle", MB_OK | MB_ICONWARNING); return; } this->Destroy(); } void PiRecycleX::Window_App(Win::Event& e) { if (e.lParam == WORK_ID) { //______________________________________________ 1. Wait for worker thread suspendedThread.WaitForExit(); btRun.Enabled = true; this->timer.Kill(1); //______________________________________________ 2. Display result wchar_t text[256]; _snwprintf_s(text, 256, _TRUNCATE, L"%.15f", estimatePi); tbxResult.Text = text; //______________________________________________ 3. Display duration _snwprintf_s(text, 256, _TRUNCATE, L"%.1f sec", stopwatch.GetSeconds()); this->Text = text; } } void PiRecycleX::btCancel_Click(Win::Event& e) { suspendedThread.cancel = true; } void PiRecycleX::Window_Timer(Win::Event& e) { if (suspendedThread.IsBusy()) { wchar_t text[256]; stopwatch.GetProgressInfo(progress.Get(), text, 256); this->Text = text; } else { this->Text = L"Done"; } } |
C++ 11 condition_variable |
Starting in version 11, the language C++ supports for condition_variable. It defines the classes condition_variable and condition_variable_any that are used to create objects that wait for a condition to become true. Thus, these classes can be used to block a thread, or multiple threads at the same time, until another thread modifies a shared variable and notifies the condition_variable. The program below starts a worker thread which executes SomeFunc. As soon as the code inside SomeFunc stars, the worker thread is blocked.Then, the main thread uses a condition_variable to unblock the worker thread. Later in the code, the main thread is blocked until the worker thread unblock it. A partir de la versión 11, el lenguaje C++ suporta: condition_variable. Este define las clases condition_variable y condition_variable_any que pueden usarse para crear objetos que esperan a que una condición sea verdadera (true). Así, estas clases pueden ser usadas para bloquear una thread, o varias threads al mismo tiempo, hasta que otra thread modifique una variable compartida y notifique la condition_variable. El programa de abajo inicia una worker thread la cual ejecute SomeFunc. Tan pronto como el código dentro de SomeFunc inicia, la worker thread es bloqueada. Entonces, la thread principal usa una condition_variable para desbloquear la worker thread. Después en el código, la thread principal se bloquea hasta que la worker thread la desbloquea. |
Program.h |
#pragma once //______________________________________ Program.h #include "resource.h" #include <thread> #include <mutex> #include <condition_variable> class Program: public Win::Dialog { public: Program() { } ~Program() { } static bool beginWorking; static bool workIsFinished; static std::mutex mtx; static std::condition_variable conditionVariable; static void SomeFunc(int a, double b); ... }; Program.cpp ... bool Program::beginWorking; bool Program::workIsFinished; std::mutex Program::mtx; std::condition_variable Program::conditionVariable; void Program::Window_Open(Win::Event& e) { beginWorking = false; workIsFinished = false; //______________________________________________ 1. Start a thread, the thread executes SomeFunc(4, 5.34); std::thread workerThread(SomeFunc, 4, 5.34); //______________________________________________ 2. Notify the workerThread and unblock it mtx.lock(); beginWorking = true; mtx.unlock(); conditionVariable.notify_one(); //______________________________________________ 3. Wait for the workerThread to send notification { std::unique_lock<std::mutex> someLock(mtx); conditionVariable.wait(someLock, []{return workIsFinished;}); } //______________________________________________ 4. Wait for worker to thread to complete workerThread.join(); } void Program::SomeFunc(int a, double b) { std::unique_lock<std::mutex> someLock(mtx); //______________________________________________ 1. Worker thread will block here conditionVariable.wait(someLock, []{return beginWorking;}); // DO SOME WORK HERE // ... workIsFinished = true; //______________________________________________ 2. Notify main thread that workIsFinished someLock.unlock(); conditionVariable.notify_one(); } |
C++ 11 future |
Starting in version 11, the language C++ supports the future sets of classes to simplify running a function (usually in a separate thread) and retrieve its results. To use these classes, you must include the header file future. A partir de la versión 11, el lenguaje C++ suporta los conjuntos de clases future para simplificar ejecutar una función (usualmente en una thread adicional) y retraer sus resultados. Para usar estas clases usted debe incluir el archivo de encabezado future. |