Adi Levin's Blog for programmers

June 29, 2009

User-Interface threads in MFC

Filed under: Multithreading — Adi Levin @ 8:01 pm
Tags: , , , ,

This article does not cover all of MFC multithreading functionality, but only a specific aspect of it: User-Interface threads. The MFC term “user-interface thread” means a thread with a message queue and message loop that dispatches messages to windows (every control in MFC, is a window).

An implementation of a UI thread from scratch requires to implement a complicated message handler, because windows must be able to respond to many messages in order to operate well. The MFC class CWinThread encapsulates the MFC message map, so it saves you the work of writing your own message routing code.

CWinThread can be used to create worker threads and UI threads. Worker threads are just threads that run a given function, not much different from calling CreateThread, so I won’t elaborate on them. I’ll focus on UI threads.

Using CWinThread to implement a UI thread – an example

To demonstrate how to use CWinThread, I wrote a dialog box that has two progress bars in it: A progress control (CProgressCtrl) that is created in the primary thread of the application, and a progress control that is created in a different UI thread.

Download source code for Visual .Net 2005

The motivation behind this is to display a moving progress bar that keeps going even when the primary thread is busy doing a long computation. It is generally better to not do any long computation in the primary thread, and always keep it available for drawing and for recieving input from the user. If there are long computations, they should be performed in worker threads. However, it is sometimes difficult to completely avoid long computations in the primary thread. During the times that the primary thread is busy, we may want to display some kind of animation, indicating that the application is alive, even though it is not responding to user input – this is the purpose of the progress control in a UI thread.

If you build the solution and run it, you’ll see that there are two progress controls. Use the buttons to start/stop the progress indicator, and to run a long computation in the primary thread or in a worker thread. You’ll notice that one progress bar stops moving when the primary thread is busy, while the other keeps moving. Both progress bar keep moving when the long computation is performed in a worker thread.

I wrote the class CProgressCtrlWithTimer that inherits from CProgressCtrl. It adds the methods Play(), Stop() and IsPlaying(), that animates the progress control, using a timer. The UI thread is implemented by the class CProgressThread, that inherits from CWinThread. The dialog owns the instance of CProgressThread as a member.

CProgressThread works as follows: It gets as input a CProgressCtrl control, which is used as a reference control, meaning that we dynamically create another control of the type CProgressCtrlWithTimer with the position, style and parent window of the reference control.

The key to make our new control respond to messages in the UI thread, is to create it in the method CProgressThread::InitInstance(), which is invoked in the UI thread, immediately after the thread is created. It is not important where the control is constructed – it is only important that its CreateWindow() method will be from the UI thread and not the main thread.

Notice that the dialog does not interact directly with the control that belongs to the UI thread. To make it Play or Stop, we post a message to the CProgressThread using PostThreadMessage. The message handlers that respond to these messages call the functions Play() or Stop() of the new control, from the UI thread.

Advertisements

June 19, 2009

Messages and Message Queues

Messages are one of the foundations of Windows programming. A Windows application is event-driven. Then means that it doesn’t call functions to get user-input. It waits for input to arrive, and responds to it. In fact, the typical situation is that there are hundreds of threads in the system at any moment, of which almost all are in a waiting state. Many of them are waiting for Messages.

The system communicates with applications via messages. For example, every mouse movement or mouse click is translated into a message, and posted to the message queue of the relevant window. Applications can use messages for inter-process and inter-window communication. Messages can also be posted to specific threads, and can be used as a means of cooperation between threads of a process.

The contents of a message

A message contains three elements: The message identifier (unsigned int), and two parameters. The message identifier tells what kind of message this is: For example, WM_PAINT is a message ordering a window to be drawn. WM_QUIT orders a window to close itself. The meaning of the parameters varies, and it depends on the kind of message being processed.

Message Queue

A message typically cannot be treated immediately when it is sent, because the recieving thread is busy doing other things. Messages stand in line to be treated at a First-In-First-Out order, with the exception of certain messages that have higher priority or lower priority than others. This queue of messages is handled by the system. Every thread potentially has a message queue attached to it. The message queue is not created until it is accessed by the thread for the first time (typically by calling GetMessage or PeekMessage).

Message Loop / Message Pump

At the top of a Windows application you will always find a message loop (also called messaeg pump). A minimal message loop may look as follows:

MSG msg;

while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)  {
        if (bRet == -1) {
            // handle the error and possibly exit
        } else {
             DispatchMessage(&msg);
        }
}

The function GetMessage waits until the is a message in the message queue, and returns the message contents. The function DispatchMessage sends the message contents to the appropriate function that performs actions in response to the message.

The contents of an MSG is as follows:

typedef struct {
    HWND hwnd;
    UINT message;
    WPARAM wParam;
    LPARAM lParam;
    DWORD time;
    POINT pt;
} MSG;

hwnd is the handle of the window to which the message is targeted. It is NULL when the message is posted to a thread rather than a window. The next 3 parameters message, wParam and lParam are the message identifier and the two parameters. time and pt signify the time the message was posted and the mouse cursor location at that time.

In the case of messages posted to a thread (i.e. hwnd is NULL), DispatchMessage is useless. You need to write your own switch-case section that will handle the message recieved from GetMessage, instead of calling DispatchMessage.

Window Procedures

There are two kinds of messages: Messages posted or sent to windows, and messages posted to threads that are not associated with any window. Most messages in the system belong to the first group (i.e. the hwnd parameter is not NULL). The purpose of DispatchMessage is to send the contents of the message to the appropriate function that should deal with it, depending on the window (hwnd) associated with this message. The system registers a specific procedure (called Window Procedure) for every type of window in the system (called Window Class). This is the prototype of a window procedure:

LRESULT CALLBACK WindowProc(      
    HWND hwnd,
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam
);

In MFC you don’t directly deal with this basic window procedure. MFC is arranged such that the window procedure calls the function CWnd::WindowProc, that has the following prototype:

virtual LRESULT CWnd::WindowProc( UINT message, WPARAM wParam, LPARAM lParam );

You typically don’t even need to override this function. It does the work of dispatching the message to specific message-handler functions, such as CWnd::OnMouseMove, CWnd::OnPaint etc…, where you implement application-specific behavior.

Posting and Sending messages to windows

The most important functions for pushing messages to the message queue are PostMessage and SendMessage. Their prortotypes are:

BOOL PostMessage(HWND hWnd,    UINT Msg,    WPARAM wParam,    LPARAM lParam);
LRESULT SendMessage(HWND hWnd,    UINT Msg,    WPARAM wParam,    LPARAM lParam);

PostMessage adds the message to the given window’s message queue, and returns immediately, without waiting for the message to be processed. SendMessage does not return until the message has been handled by the window procedure (i.e. the recieving Windows procedures has called ReplyMessage).

Messages posted through PostMessage are called queued messages. Messages sent through SendMessage are called non-queued messages, and they are first to be retrieved by GetMessage. This means that they will be treated first, before the queued messages, regardless of the order in which the messages were posted/sent.

When calling SendMessage(hWnd,…)  from the same thread that manages the window of hWnd, the system will directly call that window’s window procedure. The message will not enter the message queue at all. When SendMessage is called from a different thread, the message is written to the message queue, but has precedence over all queued messages. SendMessage will not return until the recieving thread treats the message.

Things to notice when using SendMessage

Because SendMessage blocks execution, you should use it with care. If two windows of different threads (or even different applications) communicate carelessly using SendMessage, they can reach a dead-lock, when each thread waits for the other one to complete its task. When using SendMessage you have to be sure that the recieving window is not waiting for the calling thread, and is not too busy to handle the sent message.

A way to avoid deadlocks

Suppose that a worker thread wants to run an operation on the main thread (because it update GUI elements that should only be accessed by the main thread), and it uses SendMessage() to do so. If the main thread, at that time, is waiting for some event to be signaled by the worker thread, it will result in a deadlock. One solution, is to replace the wait from WaitForSingleObject(event,INFINITY) to the following loop:

While (WaitForSingleObject(event,50)=WAIT_TIMEOUT)

PeekMessage(&msg,…);

WaitForSingleObject waits for the event to be signaled. After 50 milliseconds, if the event has not been signaled, we call PeekMessage, which will respond to sent messages (not to queued messages), thereby preventing the deadlock.

Before using SendMessage, you should consult the MSDN documentation of this function.

Things to notice when using PostMessage

When you use PostMessage there is no risk of a dead-lock, since you’re not blocking the execution. However, there is a different risk: You don’t know when the message will eventually be treated. It can be far in the future – depending on how busy the application is. By that time, the parametes lParam and wParam may become invalid (if they are pointers to data), or the entire message may be irrelevant. If such a risk exists in your application, you have to find a way to signify to the function that responds to the message, that the message is not relevant any more.

Posting messages to threads

You can also use messages as a means of inter-thread communication. It is a bit like using events, in the sense that one thread is waiting for an event, and another thread signals the event, thereby causing the first thread to react. But it enables more detailed control due to the fact that messages have three parameters – the message type, and two parameters. In order to exploit this mechanism, you need to write your own message loop in one thread, and use PostThreadMessage to post messages to the thread’s message queue.

BOOL PostThreadMessage(      
    DWORD idThread,
    UINT Msg,
    WPARAM wParam,
    LPARAM lParam
);

The thread that handles the messages should run a message loop such as the following:

while(GetMessage( &msg, -1, 0, 0 ))  {
        switch (msg.message) {
            case WM_APP: do_some_action(msg.wParam,msg.lParam); break;
            case WM_APP+1: do_some_action1(); break;
            case WM_QUIT: return;
        }
}

IMPORTANT: Before you post a message to a thread, you should make sure that the thread has a message queue. When the thread is created, it doesn’t have a message queue, until the first call to PeekMessage, or other message-queue functions. To make sure the message queue is ready, call PeekMessage before the message loop, to create the message queue, and then signal an event to let other threads know that the message queue is ready.

Notice that PeekMessage and GetMessage have similar functions. The main difference between them is that PeekMessage returns immediately, and GetMessage waits for a message to be present in the message queue.

Ranges of message identifiers

It is important to know which numbers can be used as message identifiers, if you are going to use your own messages for communicating among windows or threads. The following ranges have different meanings:

0 to WM_USER-1: Reserved for use by the system. For example, WM_PAINT, WM_MOUSEMOVE, WM_CHAR. Send messages in this range to mimic system behavior. For example – to cause a window to paint itself, or to cause a dialog to react as if the user pressed a certain button, or to cause a window to close.

WM_USER to WM_APP-1: For use by private window classes. Don’t use it to send messages to other applications unless both applications are defined to understand them.

WM_APP to 0xBFFF: Available for use by applications. These will not conflict with system messages. To use them, you need to send WM_APP+x and in the message loop or window procedure, respond to messages with the identifier WM_APP+x. This is good for any x < 16384.

0xC000 to 0xFFFF: Named messages (called “string messages”) for use by applications. Like CreateEvent is used to create a named event for inter-process communication, RegisterWindowMessage is used to get the message identifier for a given name of message. The returned number won’t be the same in different sessions.

Greater than 0xFFFF: Reserved by the system for future use.

The WM_COPYDATA message

Normal messages send only three parameters. The WM_COPYDATA message has a special behavior that enables to send larger amounts of data between processes. When sending a WM_COPYDATA message, the lParam points to data, which will be copied to the address space of the recievnig process.

To send a WM_COPYDATA, you have to use SendMessage. PostMessage will not work. The wParam and lParam have special roles: wParam is a handle to the sender window, and lParam is a poiniter to a COPYDATASTRUCT structure:

typedef struct tagCOPYDATASTRUCT {
    ULONG_PTR dwData; // Some description of the data
    DWORD cbData; // Size, in bytes, of the data.
    PVOID lpData; // Pointer to data.
} COPYDATASTRUCT, *PCOPYDATASTRUCT;

The recieving application should consider the data read-only. The recieved data in lParam is only valid in the recieving application during the processing of the message.

The WM_TIMER message

Timers also work through the message loop. A timer is a mechanism that enables you run a prescribed operation every X milliseconds. To define a timer, you use the function SetTimer:

UINT_PTR SetTimer(      
    HWND hWnd,
    UINT_PTR nIDEvent,
    UINT uElapse,
    TIMERPROC lpTimerFunc
);

The most common way to work with a timer is to associate it with a window (by sending hWnd in the first parameter). When you do so, the window procedure associated with that window will recieve WM_TIMER messages every uElapse milliseconds. The default windows procedure calls the callback function lpTimerFunc, if it is not NULL.

In MFC, you use CWnd::SetTimer:

UINT_PTR SetTimer(
   UINT_PTR nIDEvent,
   UINT nElapse,
   void (CALLBACK* lpfnTimer)(HWND,   UINT,   UINT_PTR,   DWORD)
);

which is exactly the same, but saves you the need of knowing hWnd.

In MFC, the most convenient thing is to send a NULL lpfnTimer, and treat the message using the OnTimer() event handler of the relevant window.

A WM_TIMER message is generated when the time elapses by GetMessage or PeekMessage of the appropriate thread, which is the thread in which the relevant window was created, or, in case hWnd is NULL, the thread that called SetTimer().

In the case the hWnd parameter in SetTimer() is NULL, this means that the timer is not associated with any window. It is handled in one of two ways: Either you write your own switch-case statement in the message loop, that handles WM_TIMER messages, or the default window procedure is called, and it invokes the given callback function lpfnTimer.

It is important to know that WM_TIMER is a low-priority message. It won’t be generated if there are messages waiting in the message queue, or unqueued (sent) messages. If a timer is designed to send a WM_TIMER message every 1 second, and the thread is busy doing some long computation 10 seconds, then immediately after the thread finishes the long computation, a WM_TIMER will be sent. During these 10 seconds, 9 scheduled WM_TIMER messages have been lost (or, more precisely, they have not been generated). For the exact order or priority at which messages get treated, see the remarks section of the MSDN entry on PeekMessage.

To stop a timer from working, call KillTimer. If KillTimer is called from a different thread from the thread that responds to the WM_TIMER message, it is possible that the OnTimer event handler will work once after KillTimer has finished. If this is hazardeous to your application, you have to write your own synchronization code to prevent it from happening.

June 17, 2009

What makes one programmer better than others

Filed under: programming — Adi Levin @ 6:53 pm
Tags: , ,

Programming speed and quality

What is the goal of a programmer? It is to create high quality software. Even though it is hard to define quality, it is not hard to assess the value of any module, but it can only be done over time, by observing how useful that module is and how well it responds to the varying requirements. Therefore, an experienced programmer or team-leader working closely with other programmers can easily assess the contribution of each programmer based on the modules that they have written. It is not uncommon to find that a certain programmer is 10 times more productive than his/her colleague – in some cases even 100 times more!

When facing a complaint regarding the poor quality of their programs, many programmers blame their managers or the circumstances for not giving them enough time. “If I had more time – I could do a much better job”, suggesting that the speed of programming conflicts with quality; “It’s true that we should have done that, but there was no time – we had much more urgent problems that needed to be solved” – admitting failure to foresee problems and prevent them from happening. Managers who are not familiar with programming often fail to correctly assess the value of a programmer. The simple claim that speed conflicts with quality appears to be obviously correct.

Many do not realize that speed of programming goes hand in hand with software quality. A fast programmer will finish his project sooner than expected, leaving more time for certain activities that will improve software quality: Testing, adding desired functionality, and doing infrastructure work from which other programmers may benefit as well. A fast programmer can quickly make progress and show partial results, thereby enabling decision makers to evaluate the design and to change it to better fit the requirements, before it is too late to change the design. This way, the final product is better suited to the requirements.  Writing reusable code and investing on infrastructure enhances both the speed of programming (because less code needs to be written for new modules) and the quality of the program (because code that is being used a lot is easier to debug – since bugs in it will appear more often than in code that is rarely being used).

It is not surprising, then, to see a programmer that is exceptionally fast and at the same time known for producing high quality code. It is also common to meet a programmer who is exceptionally slow, and at the same time produces really bad code, that tends to be thrown away in time, and replaced by better code.

That said, I should also stress that a programmer should not hurry too much. Each programming project has its appropriate pace, which is not dictated by product dead-lines. As explained above, writing code too slowly is not good for quality. But writing code too fast is also dangerous, if the programmer skips certain critical stages in the process, such as unit-testing, documentation, analysis and understanding of the requirements.

In conclusion, high quality of software requires to understand the right pace in which the project should develop. A skilled programmer should require more time when needed, and make the optimal use of the given time, by working efficiently and not too slowly.

Sucessful programming

Having said that, I am not suggesting that a programmer’s value should be measured by the speed of programming, in the sense of number of lines of code written per day. This is not a good measure because certain modules contribute much more than other modules, without proportion to the size of their code. A routine that is used by many other routines can have a high degree of contribution; A routine that requires less maintenance (or none at all) contributes more than a routine that needs a lot of maintenance; A routine that contains bugs can have a negative contribution to the product.

Over time, a better measure of the success of a programmer or a programming team, is the ratio between the time they spend putting out fires (treating emergency situations), and the quality-time spent working on actual enhancements. It is very important to monitor this ratio. If it deteriorates, it signifies a problem that may get worse, until it becomes extremely difficult and costly to make changes in the software.

The real challenge is not to write a program, but to write a program that will prove useful and satisfying for many years to come. This is very demanding, because it requires to foresee requirement changes and to enable easy modification of working code. It is challenging, but can be achieved by a talented and professional team of programmers.

Since a program grows and changes over time, a programmer should take measures to make it as easy as possible to understand it and modify it in the future. Therefore, communication among programmers (through good naming, coding conventions and documentation) is as important as writing code that works. Your program should be understood by people – not only by the computer.

The principle of proximity

This can be achieved by adopting certain habits and conventions. In particular, the principle of proximity – related things in the code should be as close as possible to one-another, or easy to find. For example, a declaration of a class or variable should be as close as possible to where it is being used. Functions should be kept short, such that they won’t require endless scrolling to go through them. The principle of proximity also says that documentation inside the source code is the most important part of the system documentation, because it is always there when you need it. You don’t need to look for it. It is also much easier to update comments inside the source code, when making modifications, than to update the related description of the software (such as SRS, SDD) which is written in a separate document and in different terms. In an ideal situation, the code does not require explanations at all, because it is self-documenting, due to the wise choice of names and an intuitive choice of functions, classes and modules.

The principle of visibility

Another important principle in code construction is the principle of visibility, which says that all important information should be made explicit. You don’t get a higher score for keeping secrets. On the contrary – a program should be written in a way that makes the motivation and the meaning in it as visible as possible. Good naming is crucial here. A variable that represents an angle in degrees can be called “ang_degrees”. It is then obvious for a programmer using that variable, that it needs to be converted to radians before computing its cosine. Similarly, a point in screen space and a point in 3D model coordinates should be distinguished from one-another by their names (or even their types) in a way that makes it obvious that they are different creatures, and cannot be added, subtracted or compared to one-another. Such explicit naming saves a lot of time on searching and debugging. In the same spirit, coding conventions are also helpful, because they make it easier for team members to share their code, and find their way around the code of their colleagues.

Team work and Interdependence

Effective team work is a crucial factor in the efficiency of individual programmers. Many programmers strive to be independent – to be able to work on their own with as little support as possible from their colleagues. A more effective team work is achieved when programmers are interdependent. Interdependence means that team members have mutual access to the resources (time, knowledge, source code) of their colleagues. “Interdependence is a higher value than Independence”. Programmers double and triple their efficiency by working together. As an interdependent programmer, you should:

  • Share information and discuss problems informally. Tell other team members about things you’ve learned and how you solved problems. Even after completing a project, tell others about it – even if it is outside their area of responsibility.
  • Ask others to review your code – don’t just present a block diagram. People should have access to your code and have the capability of modifying it if needed.
  • Do not defend yourself against bug reports. Don’t make excuses, saying that a certain “bug” is a “feature”. The people who report bugs to you are really helping you, and you should thank them.
  • Assign the highest priority to interaction with co-workers. Do not arrive late to meetings. Postponing meetings, not showing up, or leaving in the middle of a meeting wastes the time of several people.
  • Be accessible (available) to co-workers. If you help them willingly, they will help you when you need them.
  • Give credit to others for good work that they have done. This will reduce the tension of competition inside the team.

Programmers should share information freely, and not treat pieces of code as their private territory. The important thing is to provide the best service – not to prove that you never make mistakes. Make it your goal to provide the best service to others.

Effective team work is at its best when the team is as small as possible. At the same time, the knowledge of team members should be as diverse as possible. When hiring a new programmer – prefer one that also brings with him knowledge that is different from the knowledge of other team members. It is often preferable to hire a programmer that has good programming experience, but has no experience in the specific domain or technologies of the company. Such a programmer will be highly motivated, and will contribute a fresh point of view.

The value of knowledge

The software industry differs from other engineering fields in the speed in which ideas turn into products. Because of that, the knowledge of programmers is the most important resource. However, the knowledge of a programmer when he is hired to the job is less important to his success than his learning capabilities. Software technologies change all of the time. It is a never ending challenge for a team of programmers to always evolve – never stop studying and learning. Good programmers Study and learn all of the time; Express themselves clearly, in a way that is easy for people to understand; Distinguish between what they know and what they don’t know. There is no one who knows everything. People should feel safe to say “I don’t know” and not be lazy to seek the appropriate knowledge.

Quailty-time with the computer

The work of a programmer includes design, review, writing code, testing and debugging. But this is not enough for really good programming. You need to get to know your program, and you need to get to know the computer. Why is that? Because in many cases, the programs are so complicated, that you don’t really know what goes on inside of them.

Even if you fully understand the routine that you wrote – do you know how many times it is called, and at what circumstances? Do you know what parameters are sent to it?  Every line of code that you write should be stepped-through using the debugger, at least once. Follow the execution of your code with your eyes, and see if it proceeds as expected. Insert breakpoints in every branch of the code, to make sure that you visit it at least once. Use a profiler to measure the performance and to build the call-graph of your functions. A profiler Profiling is extremely important for code optimization. Once you find the bottle-necks of your application, you know what you need to do in order to make it run faster. If you’ve never used profiling before, you will be surprised by what it can make you find out about your own code.

When you use library functions, or functions that were writtens by other people in your company, do you really know what they are capable of ? Or are you just “copying-and-pasting”? You should look at their documentation, or, in some cases, even go through some of their source code.

Quality-time with the computer is necessary for learning new technologies. If you hear of some kind of functionality or function library that interests you, you should find the time to play around with it. Download the library, write a program that uses it, get it to work. This is the best way to learn.

Another opportunity for quality-time occurs when you find yourself looking at old pieces of code that you wrote. Perhaps you’ll find out that some of it is unnecessary, or can be replaced by more modern tools, or needs documentation. Don’t hesitate to make changes in old and running code if it makes it better.

Quality-time is when you sit alone infront of your computer, paying attention to the finest details, absorbing information, playing around.

June 16, 2009

4 things worth doing at work

Filed under: programming — Adi Levin @ 3:41 pm
Tags: , , ,

A programmer should invest his working hours, doing these four things:

1. Develop software

This includes programming, debugging, designing, reviewing code, etc… – all the activities for which you get paid at the end of the month.

2. Learn

You have to learn new technologies, as well as study carefully the old technologies that you are currently using without a full understanding. In software engineering, the distance between an idea and its usage in the product, is very small. Learning quickly pays off.

3. Teach

Anything you’ve learned, before you got this job, or during this job, could be of interest and of use to your colleagues. Some people think it is strategically better to keep information and knowledge to themselves – making them irreplaceable. This is a bad policy, because of three reasons: (a) This is disrespectful, and people want to get respect. Also, it shows that you don’t trust other people – why should they trust you then? (b) It is bad for the organization. A company’s best interest is to increase the knowledge of all programmers – not just one. (c) Everyone is replaceable.

4. Build relationships with colleagues

If you want to be able to strengthen your influence on the product and on the methods that other people use at work, or on your work-place in general, you need to have a good relationship with people around you. Be a friend. Listen, and understand others, before you expect them to listen to you. Find opportunities to accept the ideas of colleagues or subordinates – let them know you trust them.

Some programmers do not pay enough to the three last items in this list. Do you?

June 13, 2009

Asynchronous Procedure Call

Filed under: Multithreading — Adi Levin @ 8:26 pm
Tags: , , ,

There are two ways to invoke a function on a different thread – the first is by calling CreateThread. The disadvantage here is that thread creation has a large overhead. It is much more efficient to invoke a function on an existing thread. This is done by the mechanism of Asynchronous Procedure Call (APC).

Every thread has a queue of asynchronous procedure calls attached to it. Another thread can queue a function to be invoked in that thread, using the API QueueUserAPC.

DWORD WINAPI QueueUserAPC(
  __in  PAPCFUNC pfnAPC,
  __in  HANDLE hThread,
  __in  ULONG_PTR dwData
);

A call to QueueUserAPC is a request from the thread whose handle is hThread, to run the function pfnAPC with the parameter pwData. The function pfnAPC has the following prototype:

VOID CALLBACK APCProc(
  __in  ULONG_PTR dwParam
);
 

Alertable state

A thread will invoke the queued APC function only if it the thread is in alertable state. After the thread is in an alertable state, the thread handles all pending APCs in first in – first out (FIFO) order, and the wait operation returns WAIT_IO_COMPLETION.

A thread enters an alertable state by using SleepEx, WaitForSingleObjectEx, WaitForMultipleObjectsEx to perform an alertable wait operation. For examaple, by calling SleepEx(10000,TRUE), a thread enters alertable state for 10 seconds. If, during these 10 seconds, APCs are queued, it will invoke the APCs and stop sleeping.

If an application queues an APC before the thread begins running, the thread begins by calling the APC function. After the thread calls an APC function, it calls the APC functions for all APCs in its APC queue.

A trivial example

#define _WIN32_WINNT 0x0400
#include <windows.h>

DWORD WINAPI thread_function(LPVOID lpParameter) {  while (true) { SleepEx(INFINITE,TRUE); } }
VOID CALLBACK apc_function_1(ULONG_PTR dwParam) {  C* obj = (C*)dwParam; obj->do_something(); }

void main() {
   C obj;
   WORD thread_id;
   HANDLE thread_handle = CreateThread(NULL,0,thread_function,NULL,0,&thread_id);
   Sleep(1000);
   QueueUserAPC(apc_function_1, thread_handle, (ULONG_PTR)&obj);
   Sleep(1000);
}

In the above program, the main thread creates a thread that runs thread_function, and then queues apc_function_1 to run on that thread. thread_function is specially designed to run APC functions, as it enters alertable state by calling SleepEx. Notice that QueueUserAPC requires to define _WIN32_WINNT as 0x0400 or higher.

Usage example: thread_team

The following source code demonstrates how APCs can be used to efficiently run many tasks in a number of threads. We use light-weight interlocked functions for synchronization and a single event for signaling that the tasks have all been invoked: (download the source code)

thread_team.h

class thread_team
{
public:
 thread_team(int num_of_threads);
 virtual ~thread_team();
 void invoke_tasks(task_collection& tc);

private:
 void thread_function();
 friend static DWORD WINAPI thread_func(void* param);

private:
 HANDLE m_thread_handles[1024];
 HANDLE m_exit_threads_event;
 int m_num_of_threads;
};

thread_team.cpp

#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0400
#endif

#include “thread_team.h”
#include “task_collection.h”

static VOID CALLBACK apc_proc_run(ULONG_PTR dwParam)
{
 task_collection* tc = (task_collection*)dwParam;
 tc->run();
}
 
static DWORD WINAPI thread_func(void* param)
{
 thread_team* owner = (thread_team*)param;
 owner->thread_function();
 return 0;
}

void thread_team::thread_function()
{
 while (true) {
  if (WaitForSingleObjectEx(m_exit_threads_event,INFINITE,TRUE)==WAIT_OBJECT_0)
   return;
 }
}

thread_team::thread_team(int num_of_threads) :
m_num_of_threads(num_of_threads)
{
 m_exit_threads_event = CreateEvent(NULL,TRUE,FALSE,NULL);
 DWORD thread_id;
 for(int i=0;i<num_of_threads;++i)
  m_thread_handles[i] = CreateThread(NULL,1<<24,thread_func,this,0,&thread_id);
}

thread_team::~thread_team()
{
 SetEvent(m_exit_threads_event);
 WaitForMultipleObjects(m_num_of_threads,m_thread_handles,TRUE,INFINITE);
 for(int i=0;i<m_num_of_threads;++i)
  CloseHandle(m_thread_handles[i]);
 CloseHandle(m_exit_threads_event);
}

void thread_team::invoke_tasks(task_collection& tc)
{
 for(int i=0;i<m_num_of_threads;++i)
  QueueUserAPC(apc_proc_run,m_thread_handles[i],(ULONG_PTR)&tc);
}

task_collection.h:

class task_collection
{
public:
 task_collection();
 virtual ~task_collection();

 virtual void perform_task(int index) = 0;
 virtual int  get_num_of_tasks() const = 0;

 void wait_for_completion() const;

private:

 void run();
 friend VOID CALLBACK apc_proc_run(ULONG_PTR dwParam);

private:

 volatile LONG m_next_task_index;
 volatile LONG m_number_of_completed_tasks;
 HANDLE m_finish_event;
};

task_collection.cpp

#include “task_collection.h”
#include “thread_team.h”

task_collection::task_collection() : m_next_task_index(0), m_number_of_completed_tasks(0)
{
 m_finish_event = CreateEvent(NULL,FALSE,FALSE,NULL);
}

task_collection::~task_collection()
{
 CloseHandle(m_finish_event);
}

void task_collection::wait_for_completion() const
{
 WaitForSingleObject(m_finish_event,INFINITE);
}

void task_collection::run() {
 int num_of_tasks = get_num_of_tasks();
 while (m_number_of_completed_tasks<num_of_tasks) {
  LONG task_index = InterlockedIncrement(&m_next_task_index) – 1;
  if (task_index>=num_of_tasks)
   break;
  perform_task(task_index);
  LONG num_of_completed_tasks = InterlockedIncrement(&m_number_of_completed_tasks);
  if (num_of_completed_tasks == num_of_tasks)
   SetEvent(m_finish_event);
 }
}

main.cpp

#include “thread_team.h”
#include “task_collection.h”

class apply_function_to_array : public task_collection
{
public:
 apply_function_to_array(double* x, int n, int num_of_tasks) : m_x(x), m_n(n), m_num_of_tasks(num_of_tasks){}
 virtual void perform_task(int index) {
  int nx_per_task = (m_n+1) / m_num_of_tasks;
  int i0 = index * nx_per_task;
  int i1 = (index+1) * nx_per_task;
  if (i1>m_n)
   i1 = m_n;
  for(int i=i0;i<i1;++i)
   m_x[i] = func(m_x[i]);
 }
 virtual int  get_num_of_tasks() const { return m_num_of_tasks; }

private:
 double func(double x) const { return 2*x; }
 int m_n;
 int m_num_of_tasks;
 double* m_x;
};

void main()
{
#define NX 10000000
 double* x = new double[NX];
 for(int i=0;i<NX;++i)
  x[i] = 1.0;

 apply_function_to_array tc(x,NX,1000);
 thread_team tt(4);
 tt.invoke_tasks(tc);
 tc.wait_for_completion();

 delete [] x;
}

What the function main() does, is to allocate an array of 10,000,000 double-precision numbers, set them all to 1.0, and then multiply all of them by 2.0. The multiplication is subdivided into 1000 tasks (each task performs the multiplication of 10,000 numbers in the array), and the 1000 tasks are invoked by 4 threads in parallel.

The class task_collection respresents an abstract collection of tasks. To use it, you need to inherit from the class and then overload the functions peform_task(int index) and get_num_of_tasks() to define what each task does and how many tasks there are. The class apply_function_to_array is an example of such inheritance.

The class thread_team represents a simple collection of threads. Upon creation of a thread_team object, the threads are created (in this example – 4 threads), and they all run the function thread_func that immediately enters alertable state.

When thread_team::invoke_tasks is called, it simply invokes the function apc_proc_run on each of the threads, using the APC mechanism. All apc_proc_run is doing, is to execute task_collection::run().

So, the same function, task_collection::run(), is invoked by 4 concurrent threads. What it does, is to grab (repeatedly) a task that has not been invoked yet, out of the 1000 tasks, and invoke it. In order to make sure that we don’t run the same task twice by two different threads, we use InterlockedIncrement, to increment the member m_next_task_index.

We use another counter (m_number_of_completed_tasks) with an interlocked function, to count how many tasks have been completed. This is needed in order to set an event when all tasks have been completed, which allows main() to wait for the completion of all tasks, by calling task_collection::wait_for_completion().

In order to exit the threads (which is done upon destruction of the thread_team object), the destructor of thread_team signals the event m_exit_threads_event which causes the threads to exit the while(true) loop, because WaitForSingleObjectEx returns WAIT_OBJECT_0. When an APC is called, WaitForSingleObjectEx returns WAIT_IO_COMPLETION.

Next Page »

Blog at WordPress.com.