59
These classes make writing multithreadedapplications easier,and they
also provide some extra error checking compared with the native thread API.
However,using threads is still a non-trivial undertaking,especially for large
projects.Before starting a multithreaded application or adding multithreaded
features to an existing one,it is worth considering alternatives to threads to
implement the same functionality. In some situations, threads are the only
reasonable choice, such as an FTP server application that launches a new
thread for each new client.However,using an extra thread to show a progress
dialog during a long computation would be overkill.In this case,you could do
the calculations in an idle handler and call
wxWindow::Update
periodically to
update the screen. For more details, see “Alternatives to Multithreading”
toward the end of this chapter.
If you decide to use threads in your application, it is strongly recom-
mended that only the main thread call GUI functions.The wxWidgets thread
sample shows that it is possible for many different threads to call GUI func-
tions at once,but in general,it is a very poor design choice.A design that uses
one GUI thread and several worker threads that communicate with the main
one using events is much more robust and will undoubtedly save you countless
problems. For example, under Win32, a thread can only access GDI objects
such as pens,brushes,and so on,created by itself,not those created by other
threads.
For communication between threads, you can use
wxEvtHandler::Add
PendingEvent
or its short version,
wxPostEvent
.These functions have thread-
safe implementations so that they can be used for sending events between
threads.
U
SING WX
T
HREAD
If you want to implement functionality using threads,you write a class that
derives from
wxThread
and implements at least the virtual
Entry
method,which
is where the work of the thread takes place.Let’s say you wanted to use a sep-
arate thread to count the number of colors in an image.Here’s the declaration
of the thread class:
class MyThread : public wxThread
{
public:
MyThread(wxImage* image, int* count):
m_image(image), m_count(count) {}
virtual void *Entry();
private:
wxImage* m_image;
int* m_count;
};
// An identifier to notify the application when the
// work is done
#define ID_COUNTED_COLORS 100
448
Writing Multithreaded Applications Chapter 17
VB.NET PDF - Convert PDF with VB.NET WPF PDF Viewer convert PDF to text, VB.NET extract PDF pages, VB.NET Create multiple pages Tiff file from PDF document. formats with high quality, support converting PDF to PNG
extract image from pdf file; extract image from pdf java
62
The
Entry
function does the calculation and returns an exit code that will
be returnedby
Wait
(for joinable threads only).Here’s the implementation
for
Entry
:
void *MyThread::Entry()
{
(* m_count) = m_image->CountColours();
// Use an existing event to notify the application
// when the count is done
wxCommandEvent event(wxEVT_COMMAND_MENU_SELECTED,
ID_COUNTED_COLORS);
wxGetApp().AddPendingEvent(event);
return NULL;
}
For simplicity,we’re using an existing event class to send a notification to the
application when the color count is done.
Creation
Threads are created in two steps.First the object is instantiated,and then the
Create
method is called:
MyThread *thread = new MyThread();
if ( thread->Create() != wxTHREAD_NO_ERROR )
{
wxLogError(wxT(“Can’t create thread!”));
}
There are two different types of threads:the ones that you start and then for-
get about and the ones from which you are awaiting a result.The former are
called detached threads, and the latter are joinable threads.Thread type is
indicated by passing
wxTHREAD_DETACHED
(the default) or
wxTHREAD_JOINABLE
to
the constructor of a
wxThread
.The result of a joinable thread is returned by the
Wait
function.You cannot wait for a detached thread.
You shouldn’t necessarily create all threads as joinable,however,because
joinable threads have a disadvantage; you must wait for the thread using
wxThread::Wait
or else the system resources that it uses will never be freed,
and you must delete the corresponding
wxThread
object yourself (once used,it
cannot be reused). In contrast, detached threads are of the “fire-and-forget”
kind. You only have to start a detached thread, and it will terminate and
destroy itself.
This means,of course,that all detached threads must be created on the
heap because the thread will call
delete this
upon termination.Joinable threads
may be created on the stack,although usually they will be created on the heap as
well.Don’t create global thread objects because they allocate memory in their
constructor,which will cause problems for the memory checking system.
Using wxThread
449
77
Specifying Stack Size
You can indicate the desired stack size for the thread as a parameter for
Create
.Passing zero tells wxWidgets to use the platform default.
Specifying Priority
Some operating systems let the application supply a hint about how much exe-
cution time a thread needs. Call
wxThread::SetPriority
with a number
between 0 and 100, where 0 is the minimum and 100 is the maximum.The
symbols
WXTHREAD_MIN_PRIORITY
,
wxTHREAD_DEFAULT_PRIORITY,
and
wxTHREAD_MAX_
PRIORITY
are predefined, with values 0,50,and 100,respectively.You should
call
SetPriority
after calling
Create
but before calling
Run
.
Starting the Thread
After calling
Create
,the thread is not yet running.You need to call
wxThread::
Run
to start the thread,and wxWidgets will call the thread’s
Entry
function.
How to Pause a Thread or Wait for an External Condition
If a thread needs to wait for something to happen, you should avoid both
polling and idling in a loop that keeps the processor busy doing nothing (“busy
waiting”).
If you just want to wait a few seconds, then send the thread to sleep
using
wxThread::Sleep
.
If you are waiting for something else to happen, you should use a call
that blocks execution of the thread until you are notified of a change. For
example,if you are using sockets in a thread,you should use blocking socket
calls, which will simply pause or “hang” until data is available so that no
cycles are wasted.Or if you are waiting for data in a queue and are using a
joinable thread,you should block on the
Wait
method.
You might be tempted to use the
Pause
and
Resume
functions to temporar-
ily put your thread to sleep.However,there are a couple of problemswith this
approach.First, because
Pause
may be emulated on some operating systems
(notably POSIX systems), the thread must periodically call
TestDestroy
and
terminate as soon as possible if it returns
true
.Secondly,it is very difficult to
get right.An operating system may suspend you at any moment,which could
easily lead to an application deadlock because you might lock a mutex at that
moment.
So in most cases,it is not really a sound design to use
Pause
and
Resume
.
You should try to transform your code to wait for synchronization objects (see
the following section,“Synchronization Objects”).
450
Writing Multithreaded Applications Chapter 17
78
Termination
As we have mentioned, detached threads are automatically destroyed after
completion.For a joinable thread,you can simply call
wxThread::Wait
immedi-
ately,or in a GUI application,you can poll
wxThread::IsAlive
from an idle han-
dler in the main thread and only call
Wait
if
IsAlive
returns
false
.Calling
Wait
permits thread resources to be freed.Of course,a neater alternative is to sim-
ply use a detached thread and post an event when it’s done.
You can use
wxThread::Delete
to request that a thread be deleted.For this
to work,the thread must periodically call
TestDestroy
.
S
YNCHRONIZATION
O
BJECTS
In almost any use of threads,data is shared between different threads.When
two threads attempt to access the same data, whether it is an object or a
resource,then the access has to be synchronized to avoid data being accessed
or modified by more than one thread at the same time. There are almost
always so-called invariants in a program—assumptions about related ele-
ments of data,such as having a correct first element pointer in a list and hav-
ing each element point to the next element and a
NULL
pointer in the last
element. During insertion of a new element, there is a moment when this
invariant is broken.If this list is used from two threads,then you must guard
against this moment so that no other client of the list is using it in this inter-
mediate state.
It is the programmer’s responsibility to make sure that shared data is
not just grabbed by any thread but rather is accessed in a controlled manner.
This section describes the classes you can use to achieve this protection.
wxMutex
The name comes from mutual exclusionand is the easiest synchronization ele-
ment to use.It makes sure that only one thread is accessing a particular piece
of data.To gain access to the data,the application calls
wxMutex::Lock
,which
blocks (halts execution) until the resource is free.
wxMutex::Unlock
frees the
resource again. Although you can use
wxMutex
’s
Lock
and
Unlock
functions
directly, by using the
wxMutexLocker
class,you can be sure that the mutex is
always released correctly when the instance is destroyed,even if an exception
occurred in your code.
In the following example, we assume that the
MyApp
class contains an
m_mutex
member of type
wxMutex
.
void MyApp::DoSomething()
{
wxMutexLocker lock(m_mutex);
if (lock.IsOk())
Synchronization Objects
451
57
{
... do something
}
else
{
... we have not been able to
... acquire the mutex, fatal error
}
}
There are three important rulesfor using mutexes:
1. A thread cannot generally lock a mutex that is already locked (no mutex
recursion).Although there are systems that allow this,it is not portable
across all operating systems.
2. A thread cannot unlock a mutex that a different thread has locked.If you
need such a construct,you must use semaphores (discussed later).
3. If you are in a thread that is able to do other work if it cannot lock the
mutex,you should call
wxMutex::TryLock
.It returns immediately and indi-
cates whether it was able to lock the mutex (
wxMUTEX_NO_ERROR
) or not
(
wxMUTEX_DEAD_LOCK
or
wxMUTEX_BUSY
).This is especially important for the
main (GUI) thread,which should never be blocked because your applica-
tion wouldbecome unresponsive to user input.
Deadlocks
A deadlock occurs if two threads are waiting for resources that the other
thread has already acquired.So supposing that thread A has already acquired
mutex 1 and thread B has already acquired mutex 2,if thread B is now wait-
ing for mutex 1 and thread A is waiting for mutex 2,the wait would go on for-
ever.Some systems will be able to indicate this by returning the special error
code
wxMUTEX_DEAD_LOCK
from
Lock
,
Unlock,
or
TryLock
.On other systems, the
application will just hang until the user kills it.
There are two common solutions to this problem:
Fixed order. A consistent hierarchy is imposed for acquiring multiple
locks on objects. In the previous example, every thread must always
acquire mutex 1 first and then mutex 2 so that deadlock cannot occur.
Try lock.Acquire the first lock and then call
TryLock
onany subsequent
mutex.If this fails,release all locks and start again.This is a more cost-
ly approach,but you might use it when a fixed order solution is not flex-
ible enough and would result in complicated code.
452
Writing Multithreaded Applications Chapter 17
55
wxCriticalSection
A critical section is used for guarding a certain section of code rather than
data, but in practice, it is very similar to a mutex. It is only different on
those platforms where a mutex is visible outside an application and can be
shared between processes, whereas a critical section is only visible within
the application.This makes a critical section slightly more efficient—at least
on the platforms that have a native implementation.Because of this origin,
the terminology is also slightly different—a mutex may be locked (or
acquired) and unlocked (or released), whereas a critical section is entered
and left by the program.
You should try to use the
wxCriticalSectionLocker
class whenever possi-
ble instead of directly using
wxCriticalSection
for the same reasons that
wxMutexLocker
is preferable to
wxMutex
.
wxCondition
A condition variable is used for waiting on some change of state on shared
data.For example,you could have a condition indicating that a queue has data
available.The shared data itself—the queue in this example—is usually pro-
tected by a mutex.
You could try to solve the entire problem with a loop that locks a mutex,
tests the amount of available data,and releases the lock again if there is no
data.However,this is very inefficient because the loop is running all the time,
just waiting for the right moment to grab the mutex.Such situations are more
efficiently solved using conditions because the thread can block until another
thread indicates a change of state.
Multiple threads may be waiting on the same condition, in which case
you have two choices to wake up one or more of the threads. You can call
Signal
to wake one waiting thread,or you can call
Broadcast
to wakeup all
of the threads waiting on the same condition. If several predicates are sig-
naled through the same
wxCondition
,then
Broadcast
must be used;otherwise a
thread might be awakened and be unable to run because it waits for the
“wrong”predicate to become true.
wxCondition
Example
Let’s suppose we have twothreads:
A producing thread,which puts ten elements onto the queue and then
signals “queue full”and waits for “queue empty”before it starts filling the
queue again.
A consuming thread,which has to wait for “queue full”before removing
items.
Synchronization Objects
453
71
454
Writing Multithreaded Applications Chapter 17
We need one mutex,
m_mutex
,guarding the queue and two condition variables,
m_isFull
and
m_isEmpty
.These are constructed passing the
m_mutex
variable as
parameter. It is important that you always explicitly test the predicate
because a condition might have been signaled before you were waiting on it.
In pseudo-code,this is the
Entry
code for the producing thread:
while ( notDone )
{
wxMutexLocker lock(m_mutex) ;
while( m_queue.GetCount() > 0 )
{
m_isEmpty.Wait() ;
}
for ( int i = 0 ; i < 10 ; ++i )
{
m_queue.Append( wxString::Format(wxT(“Element %d”),i) ) ;
}
m_isFull.Signal();
}
Here’s the code for the consumingthread:
while ( notDone )
{
wxMutexLocker lock(m_mutex) ;
while( m_queue.GetCount() == 0 )
{
m_isFull.Wait() ;
}
for ( int i = queue.GetCount() ; i > 0 ; —i )
{
m_queue.RemoveAt( i ) ;
}
m_isEmpty.Signal();
}
The
Wait
method unlocks the mutex and then waits for the condition to be
signaled.When it returns,it has locked the mutex again,leading to a clean
synchronization.
It is important to test the predicate not only before entering but also
when
Wait
returns because something else might have been going on between
the signaling and the awakening of our thread so that the predicate is not
true anymore;there are even systems that produce spurious wakeups.
Note that a call to
Signal
might happen before the other thread calls
Wait
and,just as with
pthread
conditions,the signal is lost.So if you want to
be sure that you don’t miss a signal, you must keep the mutex associated
with the condition initially locked and lock it again before calling
Signal
.This
means that this call is going to block until
Wait
is called by another thread.
The following example shows how a main thread can launch a worker
thread,which starts running and then waits until the main thread signals it
to continue:
Documents you may be interested
Documents you may be interested