Question

I am uncertain how static global memory is managed in DLL's and shared objects. I do not know if each handles it in the same way or different on different platforms.

Consider you have a library of classes, one of those classes is a mutex class and other classes in the library will use that mutex. What is the best or safest way to allocate a mutex in the library? I can see a couple options:

  1. Make the mutex private in the class. This I can't see working because the mutex life would only be valid within the lifetime of the object. Maybe making the object a singleton and initializing it when the library is loaded (with dllattach or attribute((constructor))) would work, I am not sure.

  2. Allocate the mutex outside of the class in static global space of the library. I think this would be the best option but what exactly happens when the DLL is loaded? If I made an object static and global in a library when does it get allocated, where in the program does it get allocated? What happens if the library is loaded during runtime as opposed to when the program starts?

Any information about this is greatly appreciated!

Was it helpful?

Solution

The way memory is managed in shared images depends on specific platforms, and DLLs are specific to Microsoft Windows.

Generally, you should always avoid using global/shared static variables, as they may introduce serious problems or bugs which are hard to identify or resolve. Even singleton classes may cause several issues in C++, specially in libraries or multi-threaded applications. (And generally, using singletons are not considered good even in higher level languages.)

For guarding against mutual exclusion race conditions, the best option would be to use a scoped lock class implemented using RAII technique, alongside the shared_ptr smart pointer, which automates memory allocation and de-allocation.

The below code illustrates implementing Mutex by using Windows API and the above techniques (as well as Pimpl idiom):

// Mutex.h
#pragma once
#include <memory>

class Mutex
{
public:
    typedef unsigned long milliseconds;

    Mutex();
    ~Mutex();

    void Lock();
    void Unlock();
    bool TryLock();
    bool TimedLock(milliseconds ms);

private:
    struct private_data;
    std::shared_ptr<private_data> data;
    // Actual data is hold in private_data struct which is non-accessible.
    // We only hold a "copyable handle" to it through the shared_ptr, which 
    // prevents copying this "actual data" object by, say, assignment operators.
    // So, private_data's destructor automatically gets called only when the last
    // Mutex object leaves its scope.
};


// Mutex.cpp
#include "Mutex.h"
#include <windows.h>

struct Mutex::private_data
{
    HANDLE hMutex;

    private_data()
    {
        hMutex = CreateMutex(NULL, FALSE, NULL);
    }

    ~private_data()
    {
        // Unlock(); ?? :/
        CloseHandle(hMutex);
    }
};

Mutex::Mutex()
    : data (new private_data())
{ }

Mutex::~Mutex()
{ }

void Mutex::Lock()
{
    DWORD ret = WaitForSingleObject(data->hMutex, INFINITE);
    ASSERT(ret == WAIT_OBJECT_0);
}

void Mutex::Unlock()
{
    ReleaseMutex(data->hMutex);
}

bool Mutex::TryLock()
{
    DWORD ret = WaitForSingleObject(data->hMutex, 0);

    ASSERT(ret != WAIT_ABANDONED);
    ASSERT(ret != WAIT_FAILED);

    return ret != WAIT_TIMEOUT;
}

bool Mutex::TimedLock(milliseconds ms)
{
    DWORD ret = WaitForSingleObject(data->hMutex, static_cast<DWORD>(ms));

    ASSERT(ret != WAIT_ABANDONED);
    ASSERT(ret != WAIT_FAILED);

    return ret != WAIT_TIMEOUT;
}


// ScopedLock.h
#pragma once
#include "Mutex.h"

class ScopedLock
{
private:
    Mutex& m_mutex;

    ScopedLock(ScopedLock const&);             // disable copy constructor
    ScopedLock& operator= (ScopedLock const&); // disable assignment operator

public:
    ScopedLock(Mutex& mutex)
        : m_mutex(mutex)
    { m_mutex.Lock(); }

    ~ScopedLock()
    { m_mutex.Unlock(); }
};

Sample usage:

Mutex m1;
MyClass1 o1;
MyClass2 o2;
...

{
    ScopedLock lock(m1);

    // thread-safe operations
    o1.Decrease();
    o2.Increase();

} // lock is released automatically here upon leaving scope

// non-thread-safe operations
o1.Decrease();
o2.Increase();


While the above code will give you the basic idea, even the better option is to use high-quality C++ libraries like boost, which have mutex, scoped_lock and many other classes already available. (And fortunately C++11 makes complete coverage of synchronization classes, freeing you from having to use boost libraries.)

UPDATE:
I suggest you to search for topics about automatic garbage collection in C++ as well as the RAII technique.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top