开发者

C++ Critical Section not working

My critical section code does not work!!! Backgrounder.run IS able to modify MESSAGE_QUEUE g_msgQueue and LockSections destructor hadn't been called yet !!!

Extra code :

typedef std::vector<int> MESSAGE_LIST; // SHARED OBJECT .. MUST LOCK!

class MESSAGE_QUEUE : MESSAGE_LIST{
public:
    MESSAGE_LIST * m_pList;
    MESSAGE_QUEUE(MESSAGE_LIST* pList){ m_pList = pList; }
    ~MESSAGE_QUEUE(){ }
    /* This class will be shared between threads that m开发者_如何学Ceans any 
     * attempt to access it MUST be inside a critical section. 
     */
    void Add( int messageCode ){ if(m_pList) m_pList->push_back(messageCode); }

    int getLast()
    { 
      if(m_pList){ 
        if(m_pList->size() == 1){ 
          Add(0x0); 
        } 
        m_pList->pop_back(); 
        return m_pList->back(); 
      } 
    }
    void removeLast()
    { 
      if(m_pList){ 
        m_pList->erase(m_pList->end()-1,m_pList->end()); 
      } 
    }
};

class Backgrounder{
public:
    MESSAGE_QUEUE* m_pMsgQueue;
    static void __cdecl Run( void* args){
        MESSAGE_QUEUE* s_pMsgQueue = (MESSAGE_QUEUE*)args;
        if(s_pMsgQueue->getLast() == 0x45)printf("It's a success!");
        else printf("It's a trap!");
    }
    Backgrounder(MESSAGE_QUEUE* pMsgQueue) 
    { 
      m_pMsgQueue = pMsgQueue; 
      _beginthread(Run,0,(void*)m_pMsgQueue); 
    }
    ~Backgrounder(){ }
};

int main(){

    MESSAGE_LIST g_List;
    CriticalSection crt;
    ErrorHandler err;
    LockSection lc(&crt,&err); // Does not work , see question #2
    MESSAGE_QUEUE g_msgQueue(&g_List);
    g_msgQueue.Add(0x45); 
    printf("%d",g_msgQueue.getLast()); 
    Backgrounder back_thread(&g_msgQueue);


    while(!kbhit());
    return 0;
}

#ifndef CRITICALSECTION_H
#define CRITICALSECTION_H
#include <windows.h>
#include "ErrorHandler.h"


class CriticalSection{
    long m_nLockCount;
    long m_nThreadId;
    typedef CRITICAL_SECTION cs;
    cs m_tCS;
public:
    CriticalSection(){
        ::InitializeCriticalSection(&m_tCS);
        m_nLockCount = 0;
        m_nThreadId = 0;
    }
    ~CriticalSection(){ ::DeleteCriticalSection(&m_tCS); }
    void Enter(){ ::EnterCriticalSection(&m_tCS);  }
    void Leave(){  ::LeaveCriticalSection(&m_tCS); }
    void Try();
};


class LockSection{
    CriticalSection* m_pCS;
    ErrorHandler * m_pErrorHandler;
    bool m_bIsClosed;
public:
    LockSection(CriticalSection* pCS,ErrorHandler* pErrorHandler){
        m_bIsClosed = false;
        m_pCS = pCS;
        m_pErrorHandler = pErrorHandler;
            // 0x1AE is code prefix for critical section header
        if(!m_pCS)m_pErrorHandler->Add(0x1AE1); 
        if(m_pCS)m_pCS->Enter();
    }
    ~LockSection(){
        if(!m_pCS)m_pErrorHandler->Add(0x1AE2);
        if(m_pCS && m_bIsClosed == false)m_pCS->Leave();
    }
    void ForceCSectionClose(){
        if(!m_pCS)m_pErrorHandler->Add(0x1AE3);
        if(m_pCS){m_pCS->Leave();m_bIsClosed = true;}
    }
};

/*

Safe class basic structure;

class SafeObj
{
     CriticalSection m_cs;

public:
    void SafeMethod()
    {
        LockSection myLock(&m_cs);
        //add code to implement the method ...

    }
};



*/
#endif


Two questions in one. I don't know about the first, but the critical section part is easy to explain. The background thread isn't trying to claim the lock and so, of course, is not blocked. You need to make the critical section object crt visible to the thread so that it can lock it.

The way to use this lock class is that each section of code that you want serialised must create a LockSection object and hold on to it until the end of the serialised block:

Thread 1:

{
    LockSection lc(&crt,&err);
    //operate on shared object from thread 1
}

Thread 2:

{
    LockSection lc(&crt,&err);
    //operate on shared object from thread 2
}

Note that it has to be the same critical section instance crt that is used in each block of code that is to be serialised.


This code has a number of problems.

First of all, deriving from the standard containers is almost always a poor idea. In this case you're using private inheritance, which reduces the problems, but doesn't eliminate them entirely. In any case, you don't seem to put the inheritance to much (any?) use anyway. Even though you've derived your MESSAGE_QUEUE from MESSAGE_LIST (which is actually std::vector<int>), you embed a pointer to an instance of a MESSAGE_LIST into MESSAGE_QUEUE anyway.

Second, if you're going to use a queue to communicate between threads (which I think is generally a good idea) you should make the locking inherent in the queue operations rather than requiring each thread to manage the locking correctly on its own.

Third, a vector isn't a particularly suitable data structure for representing a queue anyway, unless you're going to make it fixed size, and use it roughly like a ring buffer. That's not a bad idea either, but it's quite a bit different from what you've done. If you're going to make the size dynamic, you'd probably be better off starting with a deque instead.

Fourth, the magic numbers in your error handling (0x1AE1, 0x1AE2, etc.) is quite opaque. At the very least, you need to give these meaningful names. The one comment you have does not make the use anywhere close to clear.

Finally, if you're going to go to all the trouble of writing code for a thread-safe queue, you might as well make it generic so it can hold essentially any kind of data you want, instead of dedicating it to one specific type.

Ultimately, your code doesn't seem to save the client much work or trouble over using the Windows functions directly. For the most part, you've just provided the same capabilities under slightly different names.

IMO, a thread-safe queue should handle almost all the work internally, so that client code can use it about like it would any other queue.

// Warning: untested code.
// Assumes: `T::T(T const &) throw()` 
//
template <class T>
class queue { 
    std::deque<T> data;
    CRITICAL_SECTION cs;
    HANDLE semaphore;
public:
    queue() { 
        InitializeCriticalSection(&cs); 
        semaphore = CreateSemaphore(NULL, 0, 2048, NULL);
    }

    ~queue() { 
        DeleteCriticalSection(&cs); 
        CloseHandle(semaphore);
    }

    void push(T const &item) {         
        EnterCriticalSection(&cs);
        data.push_back(item);
        LeaveCriticalSection(&cs);
        ReleaseSemaphore(semaphore, 1, NULL);
    }

    T pop() { 
        WaitForSingleObject(semaphore, INFINITE);
        EnterCriticalSection(&cs);
        T item = data.front();
        data.pop_front();
        LeaveCriticalSection(&cs);
        return item;
    }
};



HANDLE done;

typedef queue<int> msgQ;

enum commands { quit, print };

void backgrounder(void *qq) { 

  // I haven't quite puzzled out what your background thread
  // was supposed to do, so I've kept it really simple, executing only
  // the two commands listed above.
  msgQ *q = (msgQ *)qq;
  int command;

  while (quit != (command = q->pop()))
      printf("Print\n");
  SetEvent(done);
}

int main() { 
    msgQ q;
    done = CreateEvent(NULL, false, false, NULL);
    _beginthread(backgrounder, 0, (void*)&q);
    for (int i=0; i<20; i++)
        q.push(print);
    q.push(quit);
    WaitForSingleObject(done, INFINITE);
    return 0;
}


Your background thread needs access to the same CriticalSection object and it needs to create LockSection objects to lock it -- the locking is collaborative.


You are trying to return the last element after popping it.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜