Reputation: 3031
I have a dialog box with a button, and some other controls.
When that button is pressed a worker thread is spawned.
For easier discussion, let us just say that thread function does a lengthy job.
Each time button is clicked, new thread should spawn and do its stuff.
Dialog box should not be blocked while worker threads do their job, since user should be able to minimize it, click on other controls and so on.
On Wikipedia, i have found a term lock-free algorithm, which refers to non blocking thread synchronization.
This is why I am interested in non blocking thread synchronization. I believe that this will ensure the behavior I need.
I am new to multithreading, but I did find some articles/answered questions about it here, and have read the documentation on Microsoft about it.
Most of these use the following algorithms:
1.
Main thread declares a volatile variable ( usually int or bool ) and passes it to the worker thread. Worker thread checks the variable in a loop, and if it is not set to indicate its termination, continues to do its work. When it needs to be terminated, parent thread sets the variable.
2.
There is also a vast documentation on Microsoft's website about synchronization. There, I have found mutexes, critical sections, interlocks and much more.
The problem with these is that they always block the parent thread ( usually with WaitForSingleObject or WaitForMultipleObjects API ), until worker thread is finished.
Also, searching here, I have found a recent question ( Abort thread properly when dialog box close button is clicked ) that has helped me make a start.
In order to solve my problem I will break this question into multiple ones, and post them separately, in order to respect the rules of the StackOverflow.
So my first question for now is:
Which API/algorithm should I use to achieve non blocking thread synchronization, which will help me to achieve dialog box behaves as described above ?
I would also appreciate links to tutorials or code examples, if possible, since I had no luck with Google ( also, developers learn best through code/pseudo code ).
I will present my initial tries with these code snippets bellow, just in case they prove to be useful:
// thread function
DWORD WINAPI MyThread()
{
int result = 0;
// do something
if( /** everything is OK **/ )
return result;
else
{
result = 1;
return result;
}
}
Now the dialog box:
// button clicked handler
case IDC_BUTTON1:
{
// create thread
DWORD tID;
HANDLE th = CreateThread( NULL , 0 ,
(LPTHREAD_START_ROUTINE)MyThread ,
NULL , 0 , &tID );
if( !th )
EndDialog( hWnd, IDCANCEL );
CloseHandle( th );
}
break;
case IDCANCEL:
EndDialog( hWnd, IDCANCEL );
break;
At this point, when I run the program, activate dialog box, and click on the button, worker thread spawn up, and do well if I wait it to finish.
Yet if I close the dialog box early, the nature of my problem is the same as in the question I have found here ( I think that WaitForMultipleObjects in IDCANCEL handler can partialy fix this but I will leave it for another post ).
IMPORTANT NOTE:
Instead of firing up multiple threads, I have hidden the button that starts a thread, once it is pressed.
This means that user will have to wait for first thread to finish, before he/she is able to activate second one.
This was done for easier debugging.
Defined custom messages to indicate if thread exited gracefully, or an error happened.
#define WM_THREAD_OK ( WM_APP + 1 )
#define WM_THREAD_ERROR ( WM_APP + 2 )
Added data structure that will be passed to thread so it can communicate with a dialog, and so it can be aborted properly
struct Data
{
HWND hwnd;
bool bContinue;
};
Reworked thread function to send messages to a dialog box so it can notify dialog about errors, or graceful end.
New code ( this was based on the example from the book Charles Petzold-Programming Windows 5th ed. ):
// thread function
DWORD WINAPI MyThread( LPVOID lpvoid )
{
HRESULT hr;
volatile Data *data = ( Data* )lpvoid;
try
{
for( int i = 0; i < 10 && data->bContinue; i++ )
{
hr = // do something
if( FAILED(hr) )
throw _com_error(hr);
}
// once you leave loop, check if thread was aborted, or all was well
if( ! ( data->bContinue ) )
{
// thread was aborted, do cleanup and exit
// cleanup
return 0;
}
// if all went well, do cleanup, and "say" so to the dialog box
// do cleanup here
SendMessage( data->hwnd, WM_THREAD_OK, 0, 0 );
return 0; // exit gracefully
}
catch( _com_error & )
{
// do cleanup
SendMessage( data->hwnd, WM_THREAD_ERROR, 0, 0 );
return 1;
}
}
Here is new code for dialog box ( NOTE: dialog box is MODELESS now ):
static Data data;
HANDLE th = NULL;
// button clicked handler
case IDC_BUTTON1:
{
// create thread
DWORD tID;
th = CreateThread( NULL , 0 ,
(LPTHREAD_START_ROUTINE)MyThread ,
(LPVOID)&data, 0 , &tID );
if( !th )
DestroyWindow( hwnd );
// hide the button which activates thread
ShowWindow( GetDlgItem( hwnd, IDC_BUTTON1 ), SW_HIDE );
}
break;
case WM_THREAD_OK:
if( th )
CloseHandle( th );
ShowWindow( GetDlgItem( hwnd, IDC_BUTTON1 ), SW_SHOW );
break;
case WM_THREAD_ERROR:
if( th )
CloseHandle( threadHandle );
MessageBox( hwnd, L"Error", L"Error", MB_ICONERROR );
break;
case IDCANCEL:
data.bContinue = false; // set thread abortion flag
if( th )
{
// after I comment out below statement, thread aborts properly
// WaitForSingleObject( th, INFINITE );
CloseHandle( th );
}
DestroyWindow( hwnd ); // dialog box is now modeless one!
break;
My question is: Are there any mistakes in this code ? Can it be improved, and how ?
Thank you.
Upvotes: 2
Views: 3971
Reputation: 3604
You can proced this way:
Here is an example I have made: it is made out of a main and with C++ 11 but it can be transposed (ask me for more if you need more info or you disagree).
#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include <cstdlib>
#include <iostream>
#include <ctime>
using namespace std;
std::mutex m;
std::condition_variable cv;
atomic<bool> WorkerAvailable=true;
atomic<int> RandomVariable;
void WorkersLoop();
void worker_thread();
//this gathers the threads
void WorkersLoop()
{
std::srand(std::time(0));
int random_variable;
//infinite loop for handling all the worker threads
while (true)
{
random_variable= std::rand();
//this actually simulates the user pressing a button
if (random_variable%5==0)
{
// wait for the worker
{
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, []{return WorkerAvailable==true;});
}
std::lock_guard<mutex> lk(cout_mutex);
{
cout<<"thread started, random variable= "<<random_variable<<"\n";
}
//this launches the actual work to be done
std::thread abc(&worker_thread);
// wait for the worker
{
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, []{return WorkerAvailable==true;});
}
abc.join();
}
}
}
//this does the work you want
void worker_thread()
{
WorkerAvailable=false;
std::this_thread::sleep_for(std::chrono::seconds(1));
cout<<"thread "<< this_thread::get_id()<<"finished"<<std::endl;
WorkerAvailable=true;
cv.notify_one();
}
int main()
{
//launching the loop responsible for doing the threads
thread Loop(WorkersLoop);
Loop.join();
return 0;
}
Hope that helps, if it does not answer your question please tell me I will change or delete this post
Upvotes: 0
Reputation: 50210
you dont need lock free synchronization. Just fire up the threads and have them signal objects when complete. Do a non-blocking call on the sync handle if you dont want to block the UI thread
ie
WaitForMultipleObjects(n,lpHandles, 0, 0);
the last 0 says return rather than wait if object(s) not signalled
Upvotes: 0