HOW TO: Multicore Programming (Multiprocessing) Visual C++ Class Design Guidelines, Member Functions

Article translations Article translations
Close Close
Article ID: 558117 - View products that this article applies to.
Author: Asaf Shelly MVP
Expand all | Collapse all

SUMMARY

C++ classes provide high degree of flexibility in software design. When it comes to parallel computing this is a drawback. This is one of few articles providing recommended guidelines for application designed for multicore CPUs with OOD, specifically C++.

Abstract

The preferred system design methodology today is Object Oriented Design (OOD). This methodology divides the code into individual modules and its core is the development process. OOD puts ease of development as the top priority of the software design. Parallel systems such as SOA, Multicore Programming (Multiprocessing), and Kernel development, have the execution flow as the main focus for the software design.
It is very easy to neglect execution flow in favor of OOD. On the other hand it is also possible to produce a clean design that can support both Object Oriented methodology and clear execution flow.
This article provides the basic guidelines for a C++ based application that is suitable for the parallel environment. The information provided is based on accumulated experience with existing systems and some of it may become obsolete when Operation View modeling replaces Object Oriented modeling.

Prefer clear execution flow for member function

The execution flow is the most important aspect in a parallel application. When the source code that has a clear and simple flow the application is simple to debug and the code is easy to follow. Once of the greatest problems of OOD is multiple short functions that call each other. Code review is almost meaningless and it is extremely difficult to follow the execution flow using a debugger or by going over the execution logs.
It would thus mean a great improvement in code usability when few long functions are used. Sometimes it is easier to read a function of five lines that looks like this:
 
void SampleFunction()
{
   int ch = ReadByte();
   if ( TestValue( ch ) )
   {
      Logger.WriteLine( "OK" );
   }
   else
   {
       throw ( "Error" );
   }
}
 
The code above is simple to read and is very clean, however there is a problem with this code. It is hard to tell if any of these functions are blocking, using locks, throw any exceptions, and worse than all usually an application that uses such functions will have many such short functions. Here is a possible execution flow for such an application:
 
- SampleFunction
-   ReadByte
-     TestFileOpen
-       GetInputFileHandle
-     ReadCharFromFile
-   TestValue
-     GetHashFromInt
-     FindHash
 
The same function could be implemented in the following illustrative way:
 
void SampleFunction()
{
   bool ok = false;
   HFILE hFile = GetInputFileHandle();
   if ( hFile > 0 )
   {
      int ch = fgetchar(hFile);
      if ( ch > EOF )
      {
         ch = (ch * 0x123 + 57 ) / 33;  // get HASH
         if ( lpHashTable[ ch ] != NULL )
         {
            ok = true;
         }
      }
   }
   if (ok)
   {
      Logger.WriteLine( "OK" );
   }
   else
   {
      throw ( "Error" );
   }
}
 
This may be a longer function and it is not "pure" object oriented programming but now you can read and code and understand what it does. You can also find bugs in the execution flow by a means of a simple code review and if you try to debug it step by step you can understand where you are in the flow of the code.
Coding this way helps in finding idiotic deadlocks and reduces the number of exceptions thrown between functions, which also helps in keeping a clear execution flow.
 
 
 
 
 
 
 

Prefer not ping ponging between objects for a single flow

Object Oriented Design defines the application formation by using a set of blocks in a block diagram where each block represents an object. The design does not address runtime issues such as execution flow. For this reason it is very easy to have two different designs that are the same for OOD but are completely opposite when it comes to execution flow. If designers overlook runtime issues the design might prove to be simple to implement but extremely difficult to debug. Here is a simplified example:
 
We design a media player application. This application needs to scan all media files and read the file-name and the meta-data inside the file. Here is a list of objects according to one possible design:
  • Folder-Scanner
  • File-Finder
  • File-Attributes-Reader
  • File-Meta-Text-Info-Reader
  • File-Meta-Media-Info-Reader
 
We can call this Design A. In this design Folder-Scanner scans the folders in the computer recursively, File-Finder will find the files of the appropriate type in each folder, File-Attribute-Reader will read the file attribute after it is located for example creation time, hidden etc., File-Meta-Text-Info-Reader will read the text information from the file's metadata and File-Meta-Media-Info-Reader will read the media information from the file's metadata. This is  a pretty simple design and each programmer has a single simple task to perform and deals with a single kind of technology.
 
 
Here is an alternate design which is in a way completely different even though it is also Object Oriented. We will name this Design B:
  • File-Locator
  • File-Attributes-Reader
  • File-Meta-Data-Extractor
 
In this design File-Locator goes through all the folders on the disk recursively to locate files of the requested type. File-Attribute-Reader reads the file attributes such as creation time, hidden etc. and File-Meta-Data-Extractor will extract file metadata and then catalog the data as text info and as media info.
 
Design A is pretty strait forward. You have an object for every type of operation or technology that needs to be used and the programmer can easily implement the code into a sealed module.
Design B however is somewhat complex and demands that the programmer use more than one technology and probably requires some internal object design before coding.
 
 
When we take a look at the execution flow of the two applications Design B has a huge advantage over Design A.
Here an illustration of execution flow of Design A:
- Folder-Scanner
- -   File-Finder
- - -   File-Attributes-Reader
- - -   File-Meta-Text-Info-Reader
- - -   File-Meta-Media-Info-Reader
- - -   File-Attributes-Reader
- - -   File-Meta-Text-Info-Reader
- - -   File-Meta-Media-Info-Reader
- - -   File-Attributes-Reader
- - -   File-Meta-Text-Info-Reader
- - -   File-Meta-Media-Info-Reader
-   Folder-Scanner
- -   File-Finder
-     Folder-Scanner
- -   File-Finder
-   Folder-Scanner
- -   File-Finder
- - -   File-Attributes-Reader
- - -   File-Meta-Text-Info-Reader
- - -   File-Meta-Media-Info-Reader
- - -   File-Attributes-Reader
- - -   File-Meta-Text-Info-Reader
- - -   File-Meta-Media-Info-Reader
-   Folder-Scanner
- -   File-Finder
-     Folder-Scanner
- -   File-Finder
- - -   File-Attributes-Reader
- - -   File-Meta-Text-Info-Reader
- - -   File-Meta-Media-Info-Reader
 
When you look at this log it is very difficult to understand what went wrong in the application and where. The same problem exists when you try to step-by-step debug the application. Going through so many functions will make you lose the basic understanding of the application's state. In simple words this means that the code is not usable for debugging and error detection.
 
In comparison here is an illustration of the execution flow produced by Design B:
 
- File-Locator
- - -   File-Attributes-Reader
- - -   File-Meta-Data-Extractor
- - -   File-Attributes-Reader
- - -   File-Meta-Data-Extractor

- - -   File-Attributes-Reader
- - -   File-Meta-Data-Extractor
- File-Locator
- File-Locator

- File-Locator
- - -   File-Attributes-Reader
- - -   File-Meta-Data-Extractor

- - -   File-Attributes-Reader
- - -   File-Meta-Data-Extractor
- File-Locator
- File-Locator
- - -   File-Attributes-Reader
- - -   File-Meta-Data-Extractor
 
This is log is much cleaner and following the execution flow using the debugger is very simple and easy. We can say that this design is more user friendly for the debugger. Many times easy debugging and log readability are more important than ease of coding because sometimes coding takes only a fraction of the time required to locate a problem at production stage.
 
Parallel systems are all about execution flow. When you have 28 different threads all operating in parallel to each other you MUST have a clear execution flow and clear execution path for each thread or you will find yourself in a huge mess trying to debug and monitor the system. It is thus important that a simple execution flow is maintained and that such an execution flow is enforced by design.
 
Many times applications that were designed in the Object Oriented methodology are forcing the execution flow to travel between many objects for a single operation. It is most important that an operation cross as fewer objects as possible. Many designs assume that objects call each other for a simple operation. The recommended practice is to use as many private members as possible and as few public members as possible. As a general practice this will dramatically reduce the number of times that the execution flow jumps from one object to the other and thus help reduce an effect that is in a way a 'Spaghetti Flow' caused when the execution flow ping pongs between multiple objects.
 
 
 
 
 

Further Information and Feedback

You can further communicate with me through the following websites:
 
    Microsoft Forums for Parallel Computing:
        http://forums.microsoft.com/MSDN/default.aspx?ForumGroupID=551&SiteID=1
 
    Developer's Reference for Parallel Computing:
        http://AsyncOp.com
 
Regards,
Asaf Shelly
 
 
 
 

Properties

Article ID: 558117 - Last Review: August 22, 2008 - Revision: 1.0
APPLIES TO
COMMUNITY SOLUTIONS CONTENT DISCLAIMER
MICROSOFT CORPORATION AND/OR ITS RESPECTIVE SUPPLIERS MAKE NO REPRESENTATIONS ABOUT THE SUITABILITY, RELIABILITY, OR ACCURACY OF THE INFORMATION AND RELATED GRAPHICS CONTAINED HEREIN. ALL SUCH INFORMATION AND RELATED GRAPHICS ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. MICROSOFT AND/OR ITS RESPECTIVE SUPPLIERS HEREBY DISCLAIM ALL WARRANTIES AND CONDITIONS WITH REGARD TO THIS INFORMATION AND RELATED GRAPHICS, INCLUDING ALL IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, WORKMANLIKE EFFORT, TITLE AND NON-INFRINGEMENT. YOU SPECIFICALLY AGREE THAT IN NO EVENT SHALL MICROSOFT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY DIRECT, INDIRECT, PUNITIVE, INCIDENTAL, SPECIAL, CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF USE, DATA OR PROFITS, ARISING OUT OF OR IN ANY WAY CONNECTED WITH THE USE OF OR INABILITY TO USE THE INFORMATION AND RELATED GRAPHICS CONTAINED HEREIN, WHETHER BASED ON CONTRACT, TORT, NEGLIGENCE, STRICT LIABILITY OR OTHERWISE, EVEN IF MICROSOFT OR ANY OF ITS SUPPLIERS HAS BEEN ADVISED OF THE POSSIBILITY OF DAMAGES.

Give Feedback

 

Contact us for more help

Contact us for more help
Connect with Answer Desk for expert help.
Get more support from smallbusiness.support.microsoft.com