Multithreaded Programming on Unix in C and C++

Writing multithreaded software is much different than developing single threaded program because your software must be able to manage shared access to its memory space. This might not sound too challenging, but threads cause the complexity of your software to increase very quickly. Before embarking on a multithreaded adventure you should consider alternatives.

The steps given below are given as suggestion because there are other ways of doing what is presented here. An application must use the ideas and concepts mentioned in this article according to its needs.

Some of the things you'll need to consider are:

* Will your application break down into sub-task that can be run in parallel?

* Usually multithreaded software takes longer to test and debug plus requires more knowledge software engineering.

* If your not familiar with core
operating system concepts the program's behavior will be confusing. Implementation details of your specific version of Unix can cause some frustration.

    You can overcome complexity by concentrating on the quality of the design. Remember to manage the large as if it was small. Break things down into a sets of functions that share data and provided ways for each set to communicate with other parts of the system. Multithreaded software is like developing a series/paralell circut, your program code execute in series until it create threads that run in parallel with the other components. Program code can also be broken down like a circut and analyzed and factored as if your during circut analyses. I think of the above approach as a
Service Oriented Architecture (SOA) approach to building complex systems because, the functionality is broken down into components and each provides services to the other parts of the software.

    Design your software with synchronization in mind. Remember that your application will require more that just C and C++ types and functions written using object-oriented techniques. Problems with deadlocks and race conditions are easy to run into and hard to find. You must be very careful not to create a big mess. I worked for a company that had created such complexity through the use of threads that no one wanted to touch certain parts of the system because you could not tell what would happen.
 
When thinking about synchronization try using charts and UML diagrams to get a clear picture of what your doing.

Resource     |   Thread_A    |    Thread_B
------------------------------------------------
Resource1   |      Yes          |      No
Resource2   |      No           |      No
Resource3   |      Yes          |      Yes
-------------------------------------------------

  Because two of these threads call Resource3 you must be prepared to handle race conditions. Mutexes could be explained using the same type of chart:

Resource  |  Thread_A  |  Thread_B
-----------------------------------------
Mutex1    |     Yes         |    Yes
Mutex2    |     Yes         |    No
Mutex3    |     Yes         |    Yes
------------------------------------------

   Since Mutex1 and Mutex3 are used by Thread_A and Thread_B we must watch the ordering of our mutex locking. Deadlocks can easily occur by not knowing about how another thread is using resources.

   Having some way to view how your program threads are interacting with each other is important, especially when your design requires threads to intersect on different mutexes. Deadlocks are the result of one or more threads creating a circular sequence of locks. These sequences are not always recognized because, they could be several layers deep into the system and branching into many different logic paths.
 
It's also necessary to build debugging mechanisms into your software that catches abnormal behavior. Like the deadlocks and race conditions. You'll need a function that wakes up every so many seconds and checks the system's state. This could be a thread or a timer interrupt that acts something like the scheduler does when it checks the run-queue.

Suggesting that a procedure be created that's something like a watchdog timer or scheduler may seem like a bit much, but I'm in the habit of doing this because it's something like a guard for your application that keeps you on track once you have a stable system. Complex systems are worth the extra effort, especially when someone's life could be on the line. An example of this sort of mechanism would be a C++ class type, created to track our mutexes states across a process. We could create our C++ class object to detect deadlocks at run time. Deadlocks can occur without locking the whole system so, parts of a process may continue to operate, handling request and so on. You could have a portion of your system locked until some condition in the program code branches to the locked resource, this could take days to occur in a large system with a complex
business environment.
 
After you design your software there will still be things that you have to implement along the way because every single thing can't be anticipated. Designing software is something like a business plan. You have to check for and report on variances along the way. That being said let's consider how a threaded application component will operate.

When programming in C++ on Unix you'll have to know C programming also, and be able to work with both programming languages. You'll need to use the proper header file for C and C++. Use extern "C" { header file... } around your C headers because C++ name mangling technique will cause your C function names to be mangled. So, do this:

extern "C" {
#include ...
}
 
I don't cover the header files to include here you should have a manual on in-depth Unix programming to help you with this article. A Mutex is a system object used to manage access to critical sections of code. Don't mistaken mutexes for interrupt disabling. Interrupts are used for hardware devices and software hook into these interrupts to handle hardware request. When you disable interrupts you stop the operating system itself from interrupting the code between the disable and enable interrupts statements. Mutexes just stops other threads from entering that section of code without a lock on the mutex. When a thread need to perform some operation atomically they use the atomic data types and functions provided by the Unix systems implementation. When using threads it is extra important to understand how your datatypes are stored and handled by the system so, pay careful attention to this factor. The Mutex C++ class you might implement for debugging could look like this:

class Mutex { 
public:
    Mutex(bool run_analyzer=false);
   ~Mutex(); 
    void Lock();
    void Unlock();
private:
    pthread_mutex_t mutex;
    pthread_t tid, locked_by, analyzer_tid; 
    static pthread_mutex m_lock;
    static vector sync_info is_locked;
    static vector sync_info is_waiting;
    static int analyzer; 
    static void* scan_mutex_locks(void* arg);
    static vector<sync_info>::iterator thread_is_waiting(vector<sync_info>::iterator&);
};

The class Mutex constructor and destructor should not do too much because we don't want to be to much overhead. I used two functions to replace the POSIX mutex locking and unlocking function, they are Unlock() and Lock() these encapsulate the mutex object and POSIX API functions giving the whole process one central point of access these POSIX functions. Our private data is only used by the class implementation itself. I have a mutex class member used for the call to pthread_mutex_lock() and pthread_mutex_unlock(). The static member vectors are declared without the brackets because this article would not save while they were present. These two vectors are used to hold the locked and waiting mutexes, and thus their names is_locked and is_waiting. They both hold sync_info structures. sync_info contains the thread's TID and the address of the Mutex object the thread used to access the mutex. Both vectors are static so, they will only be created once no matter how many Mutex objects are created and will have access to every mutex used in the process.
 
Because, class Mutex is where the functionality for the actual mutex monitoring will be, any synchronization that is needed for this class must be independent of the debugging features implemented for the rest of the software. Class Mutex will contain it's own mutex object for handling critical sections within its class member functions but, will not participate in the mutex tracking itself. Also, we must take into account the libraries that we use are probably not thread-safe, for example the STL vectors will cause race conditions if not used with caution. This is what the m_lock mutex member is used for. The m_lock mutex is a static member because it must protect critical sections in all objects across the process. This causes a lot of blocking in the program when the scanning is in progress but, in a real application the analyzer thread will not run all the time. In the code shown here the Mutex class will be used all the time. There will be no switches included that allows the program to bypass class Mutexes functionality.
 
You might decide to break some of the functionality into member functions. This is what I decided to do when I created thread_is_waiting(vector sync_info ::iterator& it). This function is used to search for an item in the is_waiting vector. It simplify things for me a bit. The real task of finding deadlocks is left to the scan_mutex_locks() function. This function has a recursive algorithm:

Start:
1) Get next mutex object from the list.
2) Is it locked?
2.1) If yes, go to step 3.
2.2) If no, go to step 1.
3) Find the thread that locked it.
4) Is the thread that locked the mutex in step 3 waiting on a mutex?
4.1) If yes, go to step 5
4.2) If no, go to step 1
5) Get the mutex object the thread in step 4 is waiting on.
6) Is it the same mutex from step 1?
6.1) If yes, go to Deadlock.
6.2) If no, go to step 3.
Deadlock:
Do something to handle the deadlock.
End.

First we take a mutex from the list of locked mutexes. We already know that the mutex is locked because, it is in the is_locked vector of mutexes so, we go to the next step. The next step is to find out who locked it, this information is in the locked_by member of the Mutex class. The locked_by member is the pthread_t identifier of the thread that locked the mutex. A deadlock would be in effect if the thread that has the mutex is waiting on the same mutex or another mutex that in turn is waiting on the mutex we just referenced on the list of locked mutexes. For example, if the three conditions below are true a dealock is in effect:

1. Thread_A locked Mutex1 and waits on Mutex2
2. Thread_B locked Mutex2 and waits on Mutex3
3. Thread_C locked Mutex3 and waits on Mutex1

The deadlock in the above senerio occurs when Thread_C waits on Mutex1. Since Mutex1 is locked by Thread_A, Thread_C waits on Thread_A and Thread_A waits indirectly on Thread_C, forming a circular lock or deadlock. So, our next task is to see if that is the case. If it is we must handle the deadlock otherwise, we go back and do the previous steps, starting at step 3. The code used to implement the algorithm just examined is shown below in the next step. The rest of the class Mutex code will be posted on my website at
www.engineerjames.com.
 
Although it is possible to check for mutex deadlocks using this technique, the deadlock must be in effect before this thread is able to detect it. So, this is a run-time strategy for tracking deadlocks not a design strategy.

void* Mutex::scan_mutex_locks(void *args)

   while(true)
    {
        if(!pthread_mutex_try_lock(&m_lock)
          {
              deadlock=false;
              for(vector sync_info ::iterator it=is_locked.begin(); it!=is_locked.end(); it++)
            /* Get next mutex from the list of locked_mutexes */
                  {
                      vector sync_info ::iterator current_item=it;
                      long count=is_waiting.size();
                      for(vector sync_info ::iterator result=is_waiting.begin();;)
                         {
                              /* find out if the thread that has it locked is waitng on another thread */
                              result=thread_is_waiting(current_item); 
                              if(result==is_waiting.end() or !count)
                                 break;
/*
See if thread is waiting on itself and if it is, set the deadlock flag
otherwise update count and current_item and continue doing
the previous steps on the current_item.
*/
                              if(result->m->locked_by==it->m->locked_by)
                                {
                                    deadlock=true; 
                                    break;
                                 }
                              current_item=result; 
                             count--; 
                          } 
                      if(deadlock)
                         break; 
                   } 
              pthread_mutex_unlock(&m_lock); 
         }
     }
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
/*Name: void Mutex::Unlock(pthread_t tid)
/*Args: pthread_t tid, is the identifier of the thread that is unlocking
/*the mutex.
/*Descr: Unlock the mutex encapsulated inside this object and take the
/*Mutex object off he is locked list.
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void Mutex::Unlock(pthread_t tid)
{
     pthread_mutex_lock(&m_lock);
     if(!pthread_equal(locked_by,tid))
     {
         pthread_mutex_unlock(&m_lock);
         return; <br>     } <br>   sync_infosi(tid,this);<br>   for(vectorsync_info::iteratorit=is_locked.begin();it!=is_locked.end();it++)
    if(*it==si)
     {
         Mutex::islocked.erase(it);
         break;
     }
    locked_by=0;
    pthread_mutex_unlock(&mutex);
    pthread_mutex_unlock(&m_lock);
}


The implementation of function that locks the mutex is shown in the next step. The function name is void Mutex::Lock().
 
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
/*Name: void Mutex::Lock()
/*Descr: Lock the mutex encapsulated within this object and put the
/*Mutex object on the is_locked list after removing it from the
/*is_waiting list.
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void Mutex::Lock()
{
   pushback_waiting_thread(); /* Add waiting thread to the is_waiting list */
   pthread_thread_mutex_lock(&mutex);
   pthread_mutex_lock(&m_lock); 
   tid=pthread_self();
   locked_by=tid;
   sync_info si(tid,this);
/*
Find the thread on the is_waiting list that just locked the mutex and remove it
*/<br>  for(vectorsync_info::iteratorit=Mutex::is_waiting.begin();it!=Mutex::is_waiting.end();it++)
       {
           if(si==*it) 
             {
                Mutex::is_waiting.erase(it);
                break; 
             }
       }
/*
Add the mutex that was just locked to the is_locked list.
*/
   Mutex::is_locked.push_back(si);
   pthread_mutex_unlock(&m_lock);
}

/*
Create some threads that use the Mutex class and try stepping through the thread that scans the mutexes.
*/
 
You can try the code at your own risk. I take no responsiblity for any problems caused by using this code. If you have any questions or comments feel free to send me an e-mail at  jimsmith@engineerjames.com .
 
 
 
 

 

word to html converter html help workshop This Web Page Created with PageBreeze Free Website Builder  chm editor perl editor ide