C++ Threading vs Windows Threading vs MFC Threading vs .Net Threading

Discussion in 'C++' started by BiplabKamal, Mar 31, 2016.

  1. BiplabKamal

    BiplabKamal Member

    Joined:
    Dec 30, 2015
    Messages:
    37
    Likes Received:
    7
    Trophy Points:
    8
    Occupation:
    Software Engineer
    Location:
    Kolkata
    Not so experienced developers of Windows applications like me, always get confused in the first place when they think about the implementation of multi-threading. Not you? You might be expert though. My objective is to help novice programmers and the guys who don’t claim to be expert but do multi-threading when the requirement comes on the way. There are multiple options to implement multi-threading and thread synchronization in Windows. For unmanaged application you can either use C++ language library classes, Windows API or MFC classes and for Managed code you have to use .Net classes. To get confidence creating additional threads in your application you should have clear ideas of threads and processes in windows and how they work together. Let us first have a look on the basics of process and thread in Windows Operating System.

    Process and Threads in Windows



    In Windows every program runs under a windows process. Process has a process identifier, a virtual address space, executable code, data and resources (like files, kernel objects, input output devices etc), a security context, environment variables and at least one thread. A thread is a basic unit to which the operating system allocates it’s processor time. It is the basic entity within a process which can be scheduled for execution. A threads has a unique identifier, an entry point, exception handlers, thread local storage, scheduling priority and a thread context. The thread context includes CPU register values, kernel stack, user stack and an environment block in the address space of the process. Every process have at least one thread which is called the primary thread. While compiling and linking the code the entry point of the primary thread is decided. C++ linker looks for main() function and make it as the entry point of the executable. When the Operating System runs application the main thread gets the control of the application. Now main thread is free to create any number of threads limited by the resource availability in the system. Threads run independent of each other. This means one thread does not know that other threads are running. Also threads are preempted by OS and unpredictable. On the other hand threads of the same process share the virtual address, system resources, data and code of the process. Sharing code does not create any problem but sharing data and resources can create problem. For example if one thread is in the middle of modifying one piece of data and then another thread gets preempted which access that data, the second thread will get inconsistent value. Also there can be scenario where one thread need to wait for some action to be completed by another thread. So synchronization of resource access and communication between threads are essential part of a multi-threaded applications.

    A thread in Windows has some attributes which are decided during the creation and also can be altered before the thread start execution. When one thread creates another thread it specify those thread attributes or use the default attributes. The attributes include:
    1. Security Attributes: Controls the access to the thread object. It includes owner SID(Security Identifier), group SID, DACL (Discretionary Access Control List), SACL(System Access Control List) etc.
    2. Stack Size: Initial size of the thread stack
    3. Scheduling Priority: Priority of the thread like Critical or high priority. Thread priority is used by the OS thread scheduler.

    C++ interface for threading for Windows developers



    To implement the design requirement of multi-threading in Windows application using C++ language you have to use some libraries because the language itself does not know anything about threads. C++11 and later version added the threading support in the standard library. So if your code needs to be portable across platform and use default thread attributes you may prefer to use C++ standard library. On the other hand you may prefer to use Windows native support using Windows API if you need more control on thread attributes and you don’t want to depend on third party library or frame work. Microsoft provides MFC framework which is unmanged code that includes classes for Thread creation and management. These classes are basically object oriented wrapper of Windows API written in C++. If you are writing managed C++ code you have to use the .Net framework threading classes.

    Are you getting bored of long description? Want to jump to make your hands dirty with real code? Ok, the rest of the article will show you the executable code using different libraries. I have taken an example of a dummy application where there is a counter whose initial value is 0 and can be incremented upto a maximum value and can be decremented when it is grater than 0. It can be compared to an application which receive requests from different sources in parallel to store in a request queue and a request handler can process the requests in the queue. I kept the functionality of the examples exactly same while using different libraries or frameworks. So the output of all the programs should be same while running on Windows.

    In the examples I defined a thread safe class (MyCounter) which encapsulates the counter value and provides public methods for incrementing and decrementing the counter. Public methods are mutually exclusive, means only one thread can access the object at a time. When one thread is executing any public method all other threads trying to access the object will wait till the first thread releases the object. Then any one of the waiting threads will wake up and gain the access to the object. I am also creating two threads for incrementing the counter and one thread for decrementing the counter. The incrementing threads are elapsing a random amount of time between two increment operations. All the threads are accessing a single instance of the MyCounter class. There is a need for synchronization among threads; when an increment thread finds that the counter value has reached to the maximum allowed value it waits until counter value is decreased. Similarly when the decrement thread finds that the counter value is zero it goes into a waiting state until it’s counter value is increased. When the increment or decrement threads succeeds in it’s increment or decrement operation it generate the corresponding notification for waiting threads.

    Example 1: Use C++11 standard library concurrency classes



    Code:
    #include<iostream>
    #include<future>
    #include<mutex>
    using namespace std;
    class MyCounter
    {
    private:
        unsigned m_uCounter; // Counter value
        mutex m_mutexCounter;// mutex for exclusive access of the counter
        unsigned m_uMaxCount; // Maximum value allowed for the counter
    public:
        MyCounter(unsigned maxcount) :m_uMaxCount(maxcount),m_uCounter(0)
        {
            
        }
        bool Increment()
        {
            //Following line will lock the mutex object. Lock will be release in the destructor of the unique_lock object, it’s like smart pointer
            unique_lock<mutex> lockCounter(m_mutexCounter); 
            cout << "+MyCounter::Increment(): Thread Id = "<<this_thread::get_id() << endl;
            
            bool bRet = false;
            //Increment only if the counter value did not reach to max value
            if (m_uCounter < m_uMaxCount)
            {
                cout << "Incrementing the counter value from "<< m_uCounter;
                m_uCounter++;
                cout <<" to "<<m_uCounter << endl;
                bRet = true;
            }
            else
            {
                cout << "Count is at the max limit, could not increment" << endl;
                
            }
            cout << "-MyCounter::Increment(): Thread Id = " << this_thread::get_id() << endl<<endl;
            //Mutex will be released here
            return bRet;
        }
        int Decrement()
        {
            //Following line will lock the mutex object. Lock will be release in the destructor of the unique_lock object, it’s like smart pointer
            unique_lock<mutex> lockCounter(m_mutexCounter);
            cout << "+MyCounter::Decrement(): Thread Id = " << this_thread::get_id() << endl;
            bool bRet = false;
            //Decrement only if it is greater than zero
            if (m_uCounter > 0)
            {
                cout << "Decrementing the counter value from " << m_uCounter;
                m_uCounter--;
                cout << " to " << m_uCounter << endl;
                bRet = true;
            }
            else
            {
                cout << "Count is at min value, Could not decrement" << endl;
                
            }
            cout << "-MyCounter::Decrement(): Thread Id = " << this_thread::get_id() << endl<<endl;
            //Mutex will be released here
            return bRet;
        }
        
    };
    int main()
    {
        mutex MutexIncreased; // Used for attaching with condition variable
        mutex MutexDecreased;// Used for attaching with condition variable
        condition_variable CounterIncreased; // Used for Counter increased event
        condition_variable CounterDecreased; // Used for Counter increased event
        MyCounter counter(10);
        // Creating two lamdas: one for increment thread and one for decrement thread
        auto IncrementLamda = [&](unsigned itrcount)
        {
                for (unsigned i = 0; i < itrcount;i++)
                {
                    while (!counter.Increment())
                    {
                        //Wait for counter to be decremented
                        CounterDecreased.wait(unique_lock<mutex>(MutexDecreased));
                    
                    }
                    // Wake up the threads waiting for counter increment
                    CounterIncreased.notify_all();
                    std::this_thread::sleep_for(std::chrono::milliseconds(rand() % 100 + 1));
                }
        };
        auto DecrementLamda = [&](unsigned itrcount)
        {
            for (unsigned i = 0; i < itrcount;i++)
            {
                while (!counter.Decrement())
                {
                    //Wait for counter to be decremented
                    CounterIncreased.wait(unique_lock<mutex>(MutexIncreased));
                }
                //Wake up the threads waiting for counter decrement
                CounterDecreased.notify_all();
                std::this_thread::sleep_for(std::chrono::milliseconds(50));
            }
        };
        auto future1 = async(DecrementLamda, 25);
        auto future2 = async(IncrementLamda,10);
        auto future3 = async(IncrementLamda,15);
        future1.wait();
        future2.wait();
        future3.wait();
        return 0;
    }
    
    In the above code I used lamda expression as thread entry point, condition variable for wait and notification, mutex for mutual exclusion and std::async() for asynchronous execution. You can use thread class instead of std::async(). Advantage of std::async() is that it can capture the return value of the thread function and catch the exception thrown by the thread function.

    Example 2: Use Windows threading API



    Code:
    #include<iostream>
    #include<windows.h>
    
    using namespace std;
    class MyCounter
    {
    private:
        unsigned m_uCounter; // Counter value
        HANDLE m_hMutex;// mutex for exclusive access of the counter
        unsigned m_uMaxCount; // Maximum value allowed for the counter
    public:
        MyCounter(unsigned maxcount) :m_uMaxCount(maxcount),m_uCounter(0)
        {
            // Create a unnamed non signaled mutex with default security attributes
            m_hMutex = CreateMutexW(NULL, FALSE, NULL); 
        }
        bool Increment()
        {
            // Wait for the mutex with no timeout
            ::WaitForSingleObject(m_hMutex,INFINITE);
            cout << "+MyCounter::Increment(): Thread Id = "<< ::GetCurrentThreadId() << endl;
            bool bRet = false;
            //Increment only if the counter value did not reach to max value
            if (m_uCounter < m_uMaxCount)
            {
                cout << "Incrementing the counter value from "<< m_uCounter;
                m_uCounter++;
                cout <<" to "<<m_uCounter << endl;
                bRet = true;
            }
            else
            {
                cout << "Count is at the max limit, could not increment" << endl;
            }
            cout << "-MyCounter::Increment(): Thread Id = " << ::GetCurrentThreadId() << endl << endl;
            //Release the mutex
            ::ReleaseMutex(m_hMutex);
            return bRet;
        }
        int Decrement()
        {
            // Wait for the mutex with no timeout
            ::WaitForSingleObject(m_hMutex, INFINITE);
            cout << "+MyCounter::Decrement(): Thread Id = " << ::GetCurrentThreadId() << endl;
            bool bRet = false;
            //Decrement only if it is greater than zero
            if (m_uCounter > 0)
            {
                cout << "Decrementing the counter value from " << m_uCounter;
                m_uCounter--;
                cout << " to " << m_uCounter << endl;
                bRet = true;
            }
            else
            {
                cout << "Count is at min value, Could not decrement" << endl;
                
            }
            cout << "-MyCounter::Decrement(): Thread Id = " << ::GetCurrentThreadId() << endl << endl;
            //Release the Mutex
            ::ReleaseMutex(m_hMutex);
            return bRet;
        }
        
    };
    // Creating global handles for events and the counter object
    HANDLE hEventInCreased;
    HANDLE hEventDeCreased;
    MyCounter counter(10);
    
    // Thread procedure
    DWORD WINAPI Increment(LPVOID lpParameter)
    {
        // The number of iteration is passed as thread function parameter
        unsigned *pitrcount = (unsigned*)lpParameter;
        unsigned itrcount = *pitrcount;
        for (unsigned i = 0; i < itrcount;i++)
        {
            while (!counter.Increment())
            {
                //Wait for counter to be decremented
                ::WaitForSingleObject(hEventDeCreased, INFINITE);
            }
            // Wake up the threads waiting for counter increment
            ::SetEvent(hEventInCreased);
            ::Sleep(rand() % 100 + 1);
        }
        return 0;
    };
    
    DWORD WINAPI Decrement(LPVOID lpParameter)
    {
        unsigned *pitrcount = (unsigned*)lpParameter;
        unsigned itrcount = *pitrcount;
        for (unsigned i = 0; i < itrcount;i++)
        {
            while (!counter.Decrement())
            {
                //Wait for counter to be decremented
                ::WaitForSingleObject(hEventInCreased, INFINITE);
            }
            //Wake up the threads waiting for counter decrement
            ::SetEvent(hEventDeCreased);
            ::Sleep(50);
        }
        return 0;
    };
    int main()
    {
        // Create unnamed, non-signaled, auto reset event with default security attributes
        hEventInCreased = CreateEvent(NULL, FALSE, FALSE, NULL);
        hEventDeCreased = CreateEvent(NULL, FALSE, FALSE, NULL);    
        unsigned DecrementItr = 25;
        unsigned IncrementItr1 = 10;
        unsigned IncrementItr2 = 15;
        // Create threads with default security attributes and default stack size
        HANDLE hTh3 = ::CreateThread(NULL, 0, Decrement, &DecrementItr, 0, NULL);
        HANDLE hTh1 = ::CreateThread(NULL, 0, Increment, &IncrementItr1, 0, NULL);
        HANDLE hTh2 = ::CreateThread(NULL, 0, Increment, &IncrementItr2, 0, NULL);
        // Wait for thread to be terminated    
        ::WaitForSingleObject(hTh1, INFINITE);
        ::WaitForSingleObject(hTh2, INFINITE);
        ::WaitForSingleObject(hTh3, INFINITE);
        return 0;
    }
    
    In the above code mutex is used for mutual exclusion and event is used for communication between threads when counter is changed

    Example 3: Use MFC threading classes



    Code:
    #include<iostream>
    #include <afxwin.h>
    #include<afxmt.h>
    
    using namespace std;
    class MyCounter
    {
    private:
        unsigned m_uCounter; // Counter value
        // Create a unnamed non signaled mutex with default secutity attributes
        CMutex m_Mutex;
        unsigned m_uMaxCount; // Maximum value allowed for the counter
    public:
        MyCounter(unsigned maxcount) :m_uMaxCount(maxcount),m_uCounter(0)
        {
            
        }
        bool Increment()
        {
            // Wait for the mutex with no timeout
            m_Mutex.Lock();
            cout << "+MyCounter::Increment(): Thread Id = "<< ::GetCurrentThreadId() << endl;
            bool bRet = false;
            //Increment only if the counter value did not reach to max value
            if (m_uCounter < m_uMaxCount)
            {
                cout << "Incrementing the counter value from "<< m_uCounter;
                m_uCounter++;
                cout <<" to "<<m_uCounter << endl;
                bRet = true;
            }
            else
            {
                cout << "Count is at the max limit, could not increment" << endl;
            }
            cout << "-MyCounter::Increment(): Thread Id = " << ::GetCurrentThreadId() << endl << endl;
            
            m_Mutex.Unlock(); // Release the mutex
            return bRet;
        }
        int Decrement()
        {
            // Wait for the mutex with no timeout
            m_Mutex.Lock();
            cout << "+MyCounter::Decrement(): Thread Id = " << ::GetCurrentThreadId() << endl;
            bool bRet = false;
            //Decrement the counter only if it is greater than zero
            if (m_uCounter > 0)
            {
                cout << "Decrementing the counter value from " << m_uCounter;
                m_uCounter--;
                cout << " to " << m_uCounter << endl;
                bRet = true;
            }
            else
            {
                cout << "Count is at min value, Could not decrement" << endl;
                
            }
            cout << "-MyCounter::Decrement(): Thread Id = " << ::GetCurrentThreadId() << endl << endl;
            
            m_Mutex.Unlock(); // Release the mutex
            return bRet;
        }
        
    };
    // Create unnamed, non-signaled, auto reset event with default security attributes
    CEvent eIncreased;
    CEvent eDecresed;
    
    MyCounter counter(10);
    UINT __cdecl  Increment(LPVOID lpParameter)
    {
        unsigned *pitrcount = (unsigned*)lpParameter;
        unsigned itrcount = *pitrcount;
        for (unsigned i = 0; i < itrcount;i++)
        {
            while (!counter.Increment())
            {
                //Wait for counter to be decremented
                ::WaitForSingleObject(eDecresed.m_hObject, INFINITE);
            }
            // Wake up the threads waiting for counter increment
            eIncreased.SetEvent();
            ::Sleep(rand() % 100 + 1);
        }
        return 0;
    };
    UINT __cdecl Decrement(LPVOID lpParameter)
    {
        unsigned *pitrcount = (unsigned*)lpParameter;
        unsigned itrcount = *pitrcount;
        for (unsigned i = 0; i < itrcount;i++)
        {
            while (!counter.Decrement())
            {
                //Wait for counter to be decremented
                ::WaitForSingleObject(eIncreased, INFINITE);
            }
            //Wake up the threads waiting for counter decrement
            eDecresed.SetEvent();
            ::Sleep(50);
        }
        return 0;
    };
    int main()
    {
        unsigned DecrementItr = 25;
        unsigned IncrementItr1 = 10;
        unsigned IncrementItr2 = 15;
        // Create threads with default security attributes and default stack size
        CWinThread* pThread1 = ::AfxBeginThread(Decrement, &DecrementItr,0,0,CREATE_SUSPENDED);
        CWinThread* pThread2 = ::AfxBeginThread(Increment, &IncrementItr1,0,0, CREATE_SUSPENDED);
        CWinThread* pThread3 = ::AfxBeginThread(Increment, &IncrementItr2, 0, 0, CREATE_SUSPENDED);
    
        pThread1->m_bAutoDelete = false;
        pThread2->m_bAutoDelete = false;
        pThread3->m_bAutoDelete = false;
    
        HANDLE hTh1 = pThread1->m_hThread;
        HANDLE hTh2 = pThread2->m_hThread;
        HANDLE hTh3 = pThread3->m_hThread;
    
        pThread1->ResumeThread();
        pThread2->ResumeThread();
        pThread3->ResumeThread();
    
        // Wait for thread to be terminated    
        ::WaitForSingleObject(hTh1, INFINITE);
        ::WaitForSingleObject(hTh2, INFINITE);
        ::WaitForSingleObject(hTh3, INFINITE);
        delete pThread1;
        delete pThread2;
        delete pThread3;
        return 0;
    }
    
    In the above program I used CMutex, CEvent and CWinThread classes.

    Example 4: Use .Net framework threading classes (For managed code)



    Code:
    #include<cstdlib>
    using namespace System;
    using namespace System::Threading;
    ref class MyCounter
    {
    private:
        unsigned m_uCounter; // Counter value
         // Declare an object for locking purpose
        Object^ lock;
        unsigned m_uMaxCount; // Maximum value allowed for the counter
    public:
        MyCounter(unsigned maxcount) :m_uMaxCount(maxcount), m_uCounter(0)
        {
            // Create the lock object
            lock = gcnew Object();
        }
        bool Increment()
        {
            //Lock for exclusive access
            Monitor::Enter(lock);
            Console::WriteLine("+MyCounter::Increment(): Thread Name = " + Thread::CurrentThread->Name);
            
            bool bRet = false;
            //Increment only if the counter value did not reach to max value
            if (m_uCounter < m_uMaxCount)
            {
                Console::Write("Incrementing the counter value from "+ m_uCounter);
                m_uCounter++;
                Console::WriteLine(" to " + m_uCounter );
                bRet = true;
            }
            else
            {
                Console::WriteLine( "Count is at the max limit, could not increment");
                
            }
            Console::WriteLine("-MyCounter::Decrement(): Thread Name = " + Thread::CurrentThread->Name);
            Console::WriteLine();
            // Release the lock
            Monitor::Exit(lock);
            return bRet;
        }
        bool Decrement()
        {
            // Wait for the mutex with no timeout
            Monitor::Enter(lock);
            Console::WriteLine( "+MyCounter::Decrement(): Thread Name = " + Thread::CurrentThread->Name);
            bool bRet = false;
            //Decrement only if it is greater than zero
            if (m_uCounter > 0)
            {
                Console::Write("Decrementing the counter value from "+ m_uCounter);
                m_uCounter--;
                Console::WriteLine(" to " + m_uCounter);
                bRet = true;
            }
            else
            {
                Console::WriteLine( "Count is at min value, Could not decrement");
    
            }
            Console::WriteLine("-MyCounter::Decrement(): Thread Name = " + Thread::CurrentThread->Name);
            Console::WriteLine();
            //Release the lock
            Monitor::Exit(lock);
            return bRet;
        }
    
    };
    // Creating class containg the thread function
    public ref class ThreadFns
    {
    private:
        unsigned ItrCount;
        MyCounter^ counter;
        static AutoResetEvent^ Increased = gcnew AutoResetEvent(false);
        static AutoResetEvent^ Decreased = gcnew AutoResetEvent(false);
    public:
        ThreadFns(unsigned count, MyCounter^ C)
        {
            ItrCount = count;
            counter = C;
        }
        void Decrement()
        {
            for (unsigned i = 0; i < ItrCount;i++)
            {
                while (!counter->Decrement())
                {
                    //Wait for counter to be incremented
                    Increased->WaitOne();
                }
                //Wake up the threads waiting for counter decrement
                if (!Decreased->Set())
                {
                    Console::WriteLine(Thread::CurrentThread->Name + " Could not set the Increment event");
                    Console::WriteLine();
                }
                Thread::Sleep(50);
            }
        };
        void Increment()
        {
            for (unsigned i = 0; i < ItrCount;i++)
            {
                while (!counter->Increment())
                {
                    //Wait for counter to be decremented
                    Decreased->WaitOne();
                }
                //Wake up the threads waiting for counter increment
                if (!Increased->Set())
                {
                    Console::WriteLine(Thread::CurrentThread->Name + " Could not set the Increment event");
                    Console::WriteLine();
                }
                Thread::Sleep(rand() % 100 + 1);
            }
        };
    };
    
    int main(array<System::String ^> ^args)
    {
        unsigned DecrementItr = 25;
        unsigned IncrementItr1 = 10;
        unsigned IncrementItr2 = 15;
        MyCounter^ Counter = gcnew MyCounter(10);
        // Create threads with default security attributes and default stack size
        ThreadFns^ thfn1 = gcnew ThreadFns(DecrementItr, Counter);
        ThreadFns^ thfn2 = gcnew ThreadFns(IncrementItr1, Counter);
        ThreadFns^ thfn3 = gcnew ThreadFns(IncrementItr2, Counter);
    
        Thread^ th1= gcnew Thread(gcnew ThreadStart(thfn1,&ThreadFns::Decrement));
        Thread^ th2 = gcnew Thread(gcnew ThreadStart(thfn2, &ThreadFns::Increment));
        Thread^ th3 = gcnew Thread(gcnew ThreadStart(thfn3, &ThreadFns::Increment));
    
        th1->Name = "Decrement Thread";
        th2->Name = "Increment Thread 1";
        th3->Name = "Increment Thread 2";
    
        th1->Start();
        th2->Start();
        th3->Start();
        return 0;
    }
    
    In the above code thread function is encapsulated in a class. Monitor and Object classes are used for mutual execution. For event synchronization AutoResetEvent class is used.

    Programming with threads involves:
    • Creating thread
    • Setting the thread attributes like security parameters, stack size, thread priority etc.
    • Starting the thread
    • Stopping the thread
    • Synchronization between threads when accessing shared resources
    • Releasing the thread object
    Among above all the activities thread synchronization is the most crucial activity. The above example code is very simple scenario whereas the real life programs are much more complicated and will require much more design effort. Even in the above example a silly mistake can create a deadlock. In the example 4 in the class ThreadFns if you just remove the ‘static’ keyword from the line of code below, it will create a dead lock. Why? That is the home work...

    Code:
    static AutoResetEvent^ Increased = gcnew AutoResetEvent(false);
     
    shabbir likes this.

Share This Page

  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice