4

I have created MutexCondition class like this

/*MutexCondtion.h file*/
#ifndef MUTEXCONDITION_H_
#define MUTEXCONDITION_H_

#include <pthread.h>
#include <stdio.h>

class MutexCondition {

private:
    bool init();
    bool destroy();

protected:
    pthread_mutex_t m_mut;
    pthread_cond_t m_con;

public:
    MutexCondition(){
        init();
    }
    virtual ~MutexCondition(){
        destroy();
    }

    bool lock();
    bool unLock();
    bool wait();
    bool signal();

};
#endif /* MUTEXCONDITION_H_ */

MutexCondtion.cpp file

#include "MutexCondition.h"

bool MutexCondition::init(){
    printf("MutexCondition::init called\n");
    pthread_mutex_init(&m_mut, NULL);
    pthread_cond_init(&m_con, NULL);
    return true;
}

bool MutexCondition::destroy(){
    pthread_mutex_destroy(&m_mut);
    pthread_cond_destroy(&m_con);
    return true;
}

bool MutexCondition::lock(){
    pthread_mutex_lock(&m_mut);
    return true;
}

bool MutexCondition::unLock(){
    pthread_mutex_unlock(&m_mut);
    return true;
}

bool MutexCondition::wait(){
    pthread_cond_wait(&m_con, &m_mut);
    return true;
}

bool MutexCondition::signal(){
    pthread_cond_signal(&m_con);
    return true;
}

And I created a WorkHandler which extends the MutexCondition

#ifndef WORKHANDLER_H_
#define WORKHANDLER_H_

#include <stdio.h>
#include <stdlib.h>
#include <queue>
#include <pthread.h>
#include <stdio.h>
#include <list>

#include "MutexCondition.h"
#include "Work.h"

using namespace::std;

class WorkHandler: MutexCondition {

private:
    int m_maxThreads;

    queue<Work*> m_workQueue;
    list<pthread_t*> m_workThreadList; //Just thread IDs

    pthread_t **m_workThreads;

    void workLoop();
    bool initThreads();
    void insertWork(Work *work);
    Work* getWork();

protected:
    static void* runWorkThread(void* delegate);

public:
    WorkHandler(int maxThreads);
    virtual ~WorkHandler();
};

#endif /* WORKHANDLER_H_ */

WorkHandler.cpp file

#include "WorkHandler.h"

WorkHandler::WorkHandler(int maxThreads) {
    // TODO Auto-generated constructor stub
    m_maxThreads = maxThreads;
    initThreads();
}

WorkHandler::~WorkHandler() {
    // TODO Auto-generated destructor stub
}

void* WorkHandler::runWorkThread(void *delegate){
    printf("WorkHandler::runWorkThread called\n");

    WorkHandler *ptr = reinterpret_cast<WorkHandler*>(delegate);
    ptr->workLoop();
    return NULL;
}

void WorkHandler::workLoop(){
    printf("WorkHandler::workLoop called\n");

    //WorkHandler *ptr = reinterpret_cast<WorkHandler*>(delegate);

    while(1){
        Work *work = getWork();
    }
}

bool WorkHandler::initThreads(){

    for(int i=0; i < m_maxThreads; i++){
        pthread_t *thread(new pthread_t);
        m_workThreadList.push_back(thread);

        if(pthread_create(thread, NULL, runWorkThread, reinterpret_cast<void *>(this))!=0){
            perror("InitThreads, pthread_create error \n");
            return false;
        }

        pthread_detach(*thread);
    }

    return true;
}

void WorkHandler::insertWork(Work* w){
    printf("WorkHandler::Thread %d insertWork locking\n", pthread_self());
    lock();
    printf("WorkHandler::insertWork Locked and inserting int queue \n");
    m_workQueue.push(w);
    signal();
    unLock();
}

Work* WorkHandler::getWork(){
    printf("WorkHandler::getWork locking\n");
    lock();
    printf("WorkHandler::getWork locked\n");
    while(m_workQueue.empty()){//Need while instead of If
        printf("WorkHandler::getWork waiting...\n");
        wait();
    }
    Work *work = m_workQueue.front();
    printf("WorkHandler::getWork got a job\n");
    m_workQueue.pop();
    unLock();

    return work;
}

The problem is that I have locked the mutex variable in the getWork() function like this

    printf("WorkHandler::getWork locking\n");
    lock();
    printf("WorkHandler::getWork locked\n");

However, if I see the log statements then all threads printed these two log statements and I think this is a problem. I am not putting anything into the queue so the first thread should wait the condition variable to be signaled and it works ok. But how come other thread can enter the area behind the lock although the first thread locked and has not called the unlock() function.

I was wondering if this is working correctly. Pleas let me know if you guys can see anything I need to fix. Thanks in advance.

user800799
  • 2,883
  • 7
  • 31
  • 36
  • 2
    Just as a small recommendation if you can use `boost::threads` it will really simplify your life. – GWW Jun 16 '11 at 04:52
  • You should not rely on logs, they might get buffered, in multithreaded enviornments logs might might not reveal the real picture. – Alok Save Jun 16 '11 at 04:56
  • 1
    1) Lookup "Rule of Three". 2) Lookup RAII. 3) Read first sentence [here](http://stackoverflow.com/questions/6352280/pthread-create-error-in-c/6352434#6352434) 4) Start using boost – Martin York Jun 16 '11 at 05:06

1 Answers1

7

The reason is that that when a thread waits on a condition variable the mutex is unlocked.

This is expected behavior.

When the condition variable is signaled the thread is not released to run until the lock is re-acquired.

If you change the function to this:

Work* WorkHandler::getWork(){
          // Remoed this as it is non-determinstic when it will be printed.
    lock();
    printf("WorkHandler::getWork locked\n");
    while(m_workQueue.empty()){//Need while instead of If
        printf("WorkHandler::getWork waiting...\n");
        wait();
        printf("WorkHandler::getWork waiting DONE\n");    // Added this.
    }
    Work *work = m_workQueue.front();
    printf("WorkHandler::getWork got a job\n");
    m_workQueue.pop();
    unLock();

    return work;
}

If you then created three threads I would expect:

WorkHandler::getWork locked
WorkHandler::getWork waiting...
WorkHandler::getWork locked;
WorkHandler::getWork waiting...
WorkHandler::getWork locked
WorkHandler::getWork waiting...

For each call to signal I would expect:

WorkHandler::Thread %d insertWork locking
WorkHandler::insertWork Locked and inserting int queue
WorkHandler::getWork waiting DONE
WorkHandler::getWork got a job

No matter how fast you call signal I would always expect to see these two printed in order.
Because the thread is not released from the condition variable until it has re-acquired the lock.

Note you may see.

WorkHandler::Thread %d insertWork locking
WorkHandler::insertWork Locked and inserting int queue
WorkHandler::getWork locked                              // A previously released thread finishes and steals 
                                                         // the job before the signalled thread can aquire the lock.
WorkHandler::getWork got a job
WorkHandler::getWork waiting DONE                        // Now the released thread just goes back to waiting.
WorkHandler::getWork waiting...
Martin York
  • 257,169
  • 86
  • 333
  • 562
  • Ok..is there any other issues I need to concern before I move to next step? For the callback I am setting the call as static and passing the delegate. Inside of the static function I called the local function to run the loop. I heard people point out "Extern C" stuff but is this something that I must fix or just recommended? Thanks in advance – user800799 Jun 16 '11 at 05:20
  • Thanks your kind answer with fully explained codes! I'm really appreciated!! – user800799 Jun 16 '11 at 05:32
  • Re: "// Remoed this as it is non-determinstic when it will be printed." — that's not quite true. In most systems, if stdout is a terminal, it's line buffered, so it gets flushed after each newline. When stdout is not a terminal (e.g. a pipe), then it becomes non-deterministic when things get printed. The stdio implementation usually has internal locking, so that multithreaded printfs don't result in any kind of data corruption, but the order that strings are printed in is non-deterministic. – Adam Rosenfield Jun 16 '11 at 05:38
  • @Adam: I was not referring to the print statement. A thread printing "locking.." first is not necessarily the one that would get the lock and print the "locked". – Martin York Jun 16 '11 at 16:30