Search code examples
c++multithreadingboostmutexboost-thread

Mutex-managed threads processed in "chunks" instead of interlacing


I'm currently trying to get two boost::threads to get equal processing time on the CPU using a shared mutex called mutexCOM. As it currently stands both threads are set to run infinitely. One thread (The main thread) simply prints out "testing!" while the other thread, running the Navigation::begin() function, prints "-----shared function-----", waits one second, then prints "-----Shared function will now end...-----". The wait is meant to simulate the larger amount of processing necessary for the function that will replace this stub. Each iteration of the infinite loops is preceded within the loop by a scoped lock on mutexCOM. The expected output should be something like the following ad infinitum:

testing!
-----shared function-----
----Shared function will now end...-----
testing!
-----shared function-----
----Shared function will now end...-----

Unfortunately, something I've done causes the output instead to be:

Thread staggering

`

It seems that the iterations are being grouped together instead of being interlaced like I would expect. If anyone can explain what's going on here I would be very appreciative, as it seems that my understanding of thread management using mutexes could use some work.

My only theory is that my assumption that boost handles queuing for locking the mutex is wrong and that I need to somehow create my own queue to give the threads fair access to avoid starvation. However, I'm doubtful about this because it would basically render the point of mutexes moot as I would still need to handle all of the management on my own. At that point why not just use a semaphore?

My code is as follows. Only relevant code is included, so don't worry about the missing class structure. Assume all headers are properly included and that SharedMutex.h is included in both classes:

SharedMutex.h -

#ifndef SHARED_MUTEX_
#define SHARED_MUTEX_
#include <boost/thread/mutex.hpp>
extern boost::mutex mutexCOM;
#endif

Navigation.cpp -

Navigation::begin() {
    boost::mutex::scoped_lock lock(mutexCOM);
    cout<< "-----shared function-----" << endl;
    sleep(1);
    cout<< "----Shared function will now end...-----" << endl;
}

mainclass.cpp -

void mainclass::run() {
    this->runNav();
    while(1) {
        boost::mutex::scoped_lock lock(mutexCOM);
        cout << "testing!" << endl;
    }
}
void mainclass::runNav() {
//this->nav is an instance of Navigation within mainclass
boost::thread navThread(boost::bind(&Navigation::begin, *(this->nav)));
//this->navPtr is a pointer to the boost::thread for later management
this->navPtr=&navThread;
}

Solution

  • This is wrong:

        // this->navPtr is a pointer to the boost::thread for later management
        this->navPtr = &navThread;
    

    You're taking the address of a local variable there. That's Undefined Behaviour if you use navPtr later on. Instead, just store the thread in a member: see sample below.

    However, I'm doubtful about this because it would basically render the point of mutexes moot as I would still need to handle all of the management on my own

    Indeed. If you want task queueing, write it. Mutexes are not for that. As the name implies, mutexes are just for mutual exclusion, the basic guarantee that two blocks of code (the critical sections) do not run at the same time.

    It doesn't guarantee anything else.

    Thread starvation is indeed the issue, as can be shown by introducing yield/sleeps at some times:

    Live On Coliru

    #include <boost/thread.hpp>
    #include <boost/thread/mutex.hpp>
    #include <boost/scoped_ptr.hpp>
    #include <iostream>
    
    static boost::mutex mutexCOM;
    
    struct Navigation {
        void begin() {
            size_t count = 0;
            while(++count< 10) {
                {
                    boost::mutex::scoped_lock lock(mutexCOM);
                    std::cout << "-----shared function-----" << std::endl;
                    boost::this_thread::sleep_for(boost::chrono::milliseconds(200));
                    std::cout << "----Shared function will now end...-----" << std::endl;
                }
                boost::this_thread::sleep_for(boost::chrono::milliseconds(100)); // or boost::this_thread::yield e.g.
            }
            std::cout << "Navigation::begin() completed\n";
        }
    };
    
    struct mainclass {
        void run() {
            size_t count = 0;
            this->runNav();
            while (++count < (1ull<<20))
            {
                {
                    boost::mutex::scoped_lock lock(mutexCOM);
                    std::cout << "testing!" << std::endl;
                }
    
                boost::this_thread::yield();
            }
            std::cout << "mainclass::run() completed\n";
        }
    
        ~mainclass() {
            if (navThread.joinable())
            {
                std::cout << "Waiting for Navigation to end...\n";
                navThread.join();
            }
        }
      private:
        void runNav() {
            navThread = boost::thread(boost::bind(&Navigation::begin, *(this->nav)));
        }
    
        boost::scoped_ptr<Navigation> nav { new Navigation };
        boost::thread navThread;
    };
    
    int main() {
        mainclass instance;
        instance.run();
    }
    

    Which prints: ./a.out | uniq -c

          1 testing!
          1 -----shared function-----
          1 ----Shared function will now end...-----
       9726 testing!
          1 -----shared function-----
          1 ----Shared function will now end...-----
       5501 testing!
          1 -----shared function-----
          1 ----Shared function will now end...-----
       5197 testing!
          1 -----shared function-----
          1 ----Shared function will now end...-----
       5316 testing!
          1 -----shared function-----
          1 ----Shared function will now end...-----
       5913 testing!
          1 -----shared function-----
          1 ----Shared function will now end...-----
       5515 testing!
          1 -----shared function-----
          1 ----Shared function will now end...-----
       5639 testing!
          1 -----shared function-----
          1 ----Shared function will now end...-----
       5352 testing!
          1 -----shared function-----
          1 ----Shared function will now end...-----
       5162 testing!
          1 Navigation::begin() completed
     995253 testing!
          1 mainclass::run() completed
          1 Waiting for Navigation to end...