Hi Sander,
Welcome to the exciting world of multithreaded image processing
The following construct:
if ( ++n1 == REFRESH_COUNT )
{
n1 = 0;
n += REFRESH_COUNT;
if ( mutex->TryLock() )
{
count += n;
bool myAbort = abort;
mutex->Unlock();
if ( myAbort )
break;
n = 0;
}
}
allows a thread to communicate with the GUI thread (or the main thread; we call it the
GUI thread because only the main thread can perform direct GUI operations) to provide progress information feedback on the console. In addition, when the GUI thread receives control it can respond to a request to abort the ongoing process (for example, when the user clicks the Pause/Abort button).
A few key things must be pointed out in the code above. One is the use of Mutex::TryLock() instead of Mutex::Lock(). Thread synchronization is a main cornerstone of parallel processing. Bad thread synchronization can easily degrade performance of parallel code to the point that nonparallel code can perform better.
TryLock() attempts to lock a Mutex object. If it cannot lock it (because other thread has already locked it), it gives up immediately and returns false. Otherwise, if it successfully locks the mutex, TryLock() returns true. In contrast, Lock() waits until it can lock the mutex. In this case, TryLock() allows us to provide progress feedback at virtually zero cost in terms of thread performance, since no special efforts are done to lock mutexes. Of course, this is possible in this case because progress information isn't an essential part of the process, so a thread can continue without problems if TryLock() returns false.
Another important fact that must be pointed out, is that after having locked a mutex, it should be unlocked
as soon as possible. Note that in the code above we perform
exactly the necessary actions while the mutex is locked: we update the global
count variable, get a local copy of the global
abort flag, and then call Mutex::Unlock() immediately. Doing things fast while mutexes are locked greatly optimizes thread synchronization performance.
Finally, in the code above the REFRESH_COUNT macro defines the number of pixels that the thread can process before trying to update progress information. REFRESH_COUNT must be something like 65536, which means that the thread will process 64K pixels (the exact number isn't important) without caring about providing feedback. This is important because if we call Mutex::TryLock() too often, performance degradation is served.
Can I safely remove all references to img.Status and related code?
Of course you can. You can launch a set of threads that run without any user feedback or GUI interaction. Obviously, the user will be unable to stop your process and the GUI will be freezed. The decision depends on the processing time. For example, typically we use constructs like this:
IndirectArray<WorkerThread> threads;
int numberOfThreads = Thread::NumberOfThreads( numberOfItems, 1 );
int itemsPerThread = numberOfItems/numberOfThreads;
for ( int i = 0; i < numberOfThreads; ++i )
threads.Add( new WorkerThread( this, i*itemsPerThread, (i < numberOfThreads-1) ? (i+1)*itemsPerThread : numberOfItems ) );
for ( int i = 0; i < numberOfThreads; ++i )
threads[i]->Start( ThreadPriority::TimeCritical );
for ( int i = 0; i < numberOfThreads; ++i )
threads[i]->Wait();
threads.Destroy();
to process a set of items by nonoverlapping chunks with parallel code.
I create a target Image and tell each thread to do part of it. Since each thread writes to its own section of the image there is no collision.
If there are no concurrent writes life is much simpler with threads since you need no synchronization: just fire up your threads and wait until all of them have finished, as in the code snippet above. This is of course the best option in terms of performance.