Click here to Skip to main content
15,897,371 members
Articles / Programming Languages / C++

Spin Lock

Rate me:
Please Sign up or sign in to vote.
2.33/5 (3 votes)
16 Jun 20022 min read 168.3K   742   12  
Efficient synchronised access to shared resources
// (C) Copyright 2001
// Craig Henderson
//
// cdm.henderson@virgin.net
// http://homepage.virgin.net/cdm.henderson
//
// SpinLock.h


#ifndef __SPINLOCK_H_
#define __SPINLOCK_H_


// if we haven't got a DBG_FN macro defined for debug output, then
// define it to be a simple TRACE
#ifndef DBG_FN
#   define DBG_FN TRACE
#endif

// if we haven't got a TRACE macro defined for debug output, then
// define a dummy to compile to a null statement
#ifndef TRACE
#   define TRACE    1? (void)0 : (void)0
#endif  // TRACE

// if we haven't got an ASSERT macro defined for debugging, then
// define a dummy to compile to a null statement
#ifndef ASSERT
#   define ASSERT   1? (void)0 : (void)0
#endif  // ASSERT


// scope the class inside a namespace
// this is always good practice to avoid clashes
// with classes from external sources
namespace cdmh {


// helper function to determine if the runtime operating system is a
// member of the WindowNT family
// Returns:
//      true    for WinNT 4, Window 2000 and WinXP
//      false   for Windows 95, 98, 98SE and Me
inline bool IsWindowsNT(void)
{
    static OSVERSIONINFO osvi        = { sizeof(OSVERSIONINFO) };
    static bool          got_version = false;

    if (!got_version)
        got_version = (::GetVersionEx(&osvi) == TRUE);
    return (osvi.dwPlatformId == VER_PLATFORM_WIN32_NT);
}


// we use a trick with a numeric template parameter to enable multiple locks
// and to avoid the need for a .cpp implementation file just to instantiate
// the static m_lock member
// each object that is instantiated with the same template parameter value
// will share the lock, and different values of the template parameter will
// identify independent locks
// For example, an if an application has two classes of worker threads, each
// class of thread requires exclusive access to a shared resource, but these
// resources are independent of each other, then one class of thread can use
// CSpinLock<1> and the other can use CSpinLock<2>.
template<size_t N=1>
class CSpinLock
{
  private:
    // this is the value that will spin around the threads. the thread
    // with this value in it's m_plock member is the one that holds the
    // lock
    // this simplifies debugging as it is easy to see if a lock is held
    // by a lock object by casting the m_plock member to a char * in the
    // watch window
    enum { LockValue = 'kcol' };

    // we declare a static member which will provide us with a cross-thread
    // value storing the actual value of the lock. each object that is
    // instantiated with the same template value will share the lock, and
    // different values of the template parameter will identify independent
    // locks
    static long m_lock;

    // this is the actual lock pointer that is used in the class.
    // it can be set through a parameter to the ctor, or by default,
    // uses the above member
    mutable long *m_plock;

    // SwitchToThread() is only available on the WindowsNT family, so we bind
    // dynamically at run-time so that we can still support Win9x family
    typedef BOOL (* PFNSwitchToThread)(void);
    PFNSwitchToThread m_pfnSwitchToThread;

  protected:
    // if these methods are not defined, then the compiler
    // will generate them if we try to use them.
    // we declare them but do not implement them as they are unused,
    // but if they are called in error, then we will get a linker
    // error. if we did not do this, the compiler will generate a basic
    // memcpy version, but that will be incorrect as the m_data vector
    // will not get copied correctly
    CSpinLock(const CSpinLock &);
    CSpinLock &operator=(const CSpinLock &);

  public:
    // this is the only public ctor
    // the parameter is optional, and can be omitted in most circumstances
    // to provide the default functionality. if the outer algorithm wants to
    // control the lock object more closely, then this parameter can be used
    explicit CSpinLock(long *plock = 0)
    {
        // if we haven't been supplied a lock variable, then use our own
        if (plock == NULL)
            m_plock = &m_lock;
        else
            m_plock = plock;

        // SwitchToThread() is only available on the WindowsNT family, so
        // we bind dynamically at run-time so that we can still support
        // Win9x family
        m_pfnSwitchToThread = NULL;
        if (cdmh::IsWindowsNT())
        {
            HINSTANCE hInst = ::LoadLibrary("Kernel32.dll");
            if (hInst != NULL)
                m_pfnSwitchToThread = (PFNSwitchToThread)::GetProcAddress(hInst, "SwitchToThread");
        }
    }

    // class dtor
    ~CSpinLock()
    {
        // if this assert fails, then the object still holds the lock
        // when in is going out of scope
        ASSERT(!this->has_lock());
    }

    void lock(void) const
    {
        // output to the debugger
        DBG_FN("0x%08x waiting for lock\n", this);

        // this is why it is called a spin lock; we simply sit in a loop
        // until the lock is released by the holding object. to make the
        // algorithm more efficient, we reliquish the processor to another
        // thread if we fail to get the lock
        // note that this is inefficient for anything more than the smallest
        // interval, as the execution thread does not block while the lock
        // is held
        while (::InterlockedExchange(m_plock, LockValue) != 0)
        {
            TRACE("0x%08x can't get lock\n", this);

            if (m_pfnSwitchToThread)
                m_pfnSwitchToThread();
        }
    }

    // release the lock
    void unlock(void) const
    {
        // if this assert fails, then the object does not have the lock
        // while this is not an error in itself, and will not have any
        // adverse side effects, it generally signifies an error in the
        // outer algorithm
        ASSERT(this->has_lock());

        // reset the lock value
        ::InterlockedExchange(m_plock, 0);

        // output to the debugger
        TRACE("0x%08x released lock\n", this);
    }

    // returns true/false to signify if the object currently holds the lock
    bool has_lock(void) const
    {
        return (*m_plock == LockValue);
    }
};


// instantiate the static memory and initialise it
// to zero - the unlocked state
template<int N> long CSpinLock<N>::m_lock = 0;


}   // namespace cdmh

#endif __SPINLOCK_H_

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Technical Lead
United Kingdom United Kingdom
Craig graduated with a B.SC. Honours in Computing for Real Time Systems from the University of the West of England, Bristol in 1995 after completing an HND in Computing in 1993.

Comments and Discussions