Recommended

Multicore community

 

Articles

Intel.com

Microsoft.co.il

 

Community

Microsoft Forums

Intel's Forum

Intel's Multicore Community

 

Resources

http://msdn.com/concurrency

Intel Multicore

NVidia Multicore GPU

 

Downloads

.Net Parallel Extensions

Intel's TBB

WinModules   

 

Tools

AsyncOp Logger

Intel thread analysis

Intel VTune

 

Contact

Asaf Shelly

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

ichilov

-->

 

 

 

 

 

 
2 / 1
 
 
 
 
 
 
 

Thread Cleanup

A thread is a system object and as such it has to be destroyed. An object has to perform cleanup upon destruction.

It is possible to terminate an application without performing object cleanup for C++ objects but a thread is a system object and therefore the system does some of the cleaning for us even if the application had been terminated. A simple example is releasing a MUTEX object that was owned by the thread.

There are still things that the operating system will not cleanup for the thread and these are usually related to logic that is found in our code. For example only our code knows when it is allowed to delete a buffer in memory and that it is no longer used, and only our code knows the appropriate C++ destructor for an object.

It is good practice to perform all cleanup in our code at all times and not to let the system do the cleanup for us.

Here is a list of considerations for thread cleanup:

Free Memory

Data and buffers on the stack belong to the thread and are 'erased' when the thread exits. Typically destructors are called for these objects as the thread steps out of scopes to exit.

Buffers that were allocated on the Heap are a different story. These buffers has no owner thread and can be shared between threads of the same process. It is possible that one thread allocated buffers for another thread and then the first thread exits. Only the logic in our code can decide who is the owner thread of the buffer and only the owner is allowed to deallocate the buffer.

A thread that is the owner of a buffer is usually the last thread to have the pointer to that buffer. This thread has to delete the buffer on exit or the buffer will stay in memory with no owner and the application will have a Memory Leak.

Every thread must free the memory buffers that it owns.

 

Close Handles

System handles are also shared between threads of the same process. Just like memory buffers, system handles have an owner thread that is determined by the logic in our code.

The owner thread is usually the last thread to have the valid Handle to the system object. This thread must close the Handle by calling the appropriate system API. If the owner thread exits without performing this cleanup then the application will keep owning the system resource but there is no way to free it because the value of the Handle is lost. This application has a Resource Leak.

Every thread must close all the system Handles that it owns.

 

Stop waiting threads

In many designs and design patterns for parallel computing systems there are threads that we use for performing Wait operations. Examples are a thread waiting for communication, a thread waiting for an operation to complete, etc. These threads are paused in a Wait State or looping on Wait. Common design is that there is a thread that is waiting on a resource as its owner and all other threads are serviced by this thread. When a thread terminates it has to notify the waiting thread that it does not need the data any more. If it does not tell the waiting thread that the data is no longer required, the thread might keep waiting and process the unnecessary data and waste processing power. The waiter thread could start a reaction as required that might cause other threads to activate and run other tasks. Sometimes such a thread will relay the data and then go back to enter the wait state.

A thread must stop all thread waiting in its behalf and must notify all other threads that it canceled all of its operations.

If a thread has created another thread and is its owner then it must also close the owned thread and allow it to perform proper cleanup.

 

Queue

Most Tasks and threads use input queues. These reduce Wait times and allow better management of resources in comparison to locking on a resource and using it based on thread priority instead of event priority. An input queue may prioritize different events by the type or the data contained by going over the queued items and peeking at the data.

When a thread exits it must attend to the queue. It is possible that there are important messages there. A simple example is a storage queue. The application might use an internal disk queue to improve write performance. When the application exits all threads will try to save settings to the disk and they do that by posting the data to the queue and continue with their cleanup. If the disk queue thread starts performing the cleanup when the command to exit was issued then it will destroy the queue and all other resources. This thread should postpone the cleanup until it had emptied its queue. It is possible that the application will tell the thread to exit after all other threads report that they finished their cleanup but even then it is possible that the settings to write to the disk are still in queue and were not even processed.

A thread must attend to its queue before closing and it is the design that should specify when the thread is allowed to start with the cleanup sequence.

 

Cancel Callbacks

Many times threads use callbacks or APCs to be notified that something happened, instead of waiting for many events. The thread has to register to the callback with a Library or with another thread.

On cleanup the thread that registered must cancel the registration or the library will call the callback function after the thread has cleaned up the data, which may result in access to uninitialized data and pointers. Some times canceling the callback will stop a thread that is waiting in the background. It is possible that canceling a callback will send a response such as calling the callback with a 'user abort' parameter. In such cases the callback should be called before starting the relevant cleanup and the exiting thread should stall destruction until the stage of the callback cleanup is complete. This is to be defined in application design.

 

Cancel I/O

Threads wait on system resources. When the thread stops it will also stop waiting on the resource. When the thread is waiting on an operation for example writing to disk, it should also cancel this operation. I/O operations are almost always slower than working with non-I/O resources. This means that long after the thread exited there are still pending I/O operations. For example if your thread writes 2GB to a Flash device it would take a few minutes. The thread can send all the buffers to the system below and go back to work. The system will send the buffers one by one to the Flash drive. If the thread decided that the operation is no longer important and exits then the write operation should be canceled.

Any I/O that the thread initiated and manages has to be canceled before the thread exits as part of the thread's cleanup.

 

Complete Communication

Communication has many layers. Some of these layers are managed by the system for us. For example a TCP socket will manage the Hand Shake sequence for us where as UDP will only manage the layer below that. The majority of communication systems have an internal communication state above the one that the system implements. An example is the Session that browsers keep on top of the TCP layer. If a browser exits without telling the host that it did then the session will stay open. If only one browser is allowed per user and the user is active on the dead session then the system will lock that user out.

A thread must complete the communications before exiting. Usually this means notifying the other side that the communication ends. Most times this includes completing a sequence. For example sending the rest of the document to the printer or sending the rest of the rest of the 'raw data' buffer to the other side so that the control 'end communication' can be sent.

 

Current Task

Threads should perform cleanup as soon as possible and exit when the command to exit was given. On the other hand the thread should complete its current task unless a Cancel Operation command was given to it. If the thread is in the middle of a file update then it has to complete this task or the file might be corrupt. If the thread is updating a buffer then it has to finish this update. Some tasks include sequences of operations for example a thread that should delete a file, copy a file, rename it, and mark it as hidden. If this operation terminates during the flow then instead of a hidden file called 'settings.dat' that the application uses we will get a visible file called '~settings.tmp' and the application will reset to defaults on the next run and lose all user information.

Threads should always complete current tasks before exiting.