[Из песочницы] Портабельный RWLock для Windows

RWLock — это такой примитив синхронизации, позволяющий одновременное чтение и эксклюзивную запись. Т.е. чтение блокирует запись, но не блокирует чтение других тредов, а запись блокирует все.

Так вот, этот весьма полезный примитив имеется в posix threads и в Windows от Vista и далее. Для Windows XP/2003 приходится изготавливать сей, весьма полезный, примитив из двух критических секций и события.

Покажем как это выглядит (код любезно предоставлен StackOverflow и слегка переведен с C на C++):

class RWLockXP // Implementation for Windows XP
{
public:
    RWLockXP()
        : countsLock(),
          writerLock(),
          noReaders(),
          readerCount(0),
          waitingWriter(FALSE)
    {
        InitializeCriticalSection(&writerLock);
        InitializeCriticalSection(&countsLock);

       /*
        * Could use a semaphore as well.  There can only be one waiter ever,
        * so I'm showing an auto-reset event here.
        */
        noReaders = CreateEvent (NULL, FALSE, FALSE, NULL);
    }

    ~RWLockXP()
    {
        writerLock.destroy();
        readerCountLock.destroy();
        noReaders.destroy();
    }

    void readLock()
    {
        /**
         * We need to lock the writerLock too, otherwise a writer could
         * do the whole of rwlock_wrlock after the readerCount changed
         * from 0 to 1, but before the event was reset.
         */

        EnterCriticalSection(&writerLock);
        EnterCriticalSection(&countsLock);
        ++readerCount;
        LeaveCriticalSection(&countsLock);
        LeaveCriticalSection(&writerLock);
    }
    void readUnLock()
    {
        EnterCriticalSection(&countsLock);
        assert (readerCount > 0);
        if (--readerCount == 0)
        {
            if (waitingWriter)
            {
                /*
                 * Clear waitingWriter here to avoid taking countsLock
                 * again in wrlock.
                 */
                waitingWriter = FALSE;
                SetEvent(noReaders);
            }
        }
        LeaveCriticalSection(&countsLock);
    }

    void writeLock()
    {
        EnterCriticalSection(&writerLock);
        /*
         * readerCount cannot become non-zero within the writerLock CS,
         * but it can become zero...
         */
        if (readerCount > 0)
        {
            EnterCriticalSection(&countsLock);

            /* ... so test it again.  */
            if (readerCount > 0)
            {
                waitingWriter = TRUE;
                LeaveCriticalSection(&countsLock);
                WaitForSingleObject(noReaders, INFINITE);
            }
            else
            {
                /* How lucky, no need to wait.  */
                LeaveCriticalSection(&countsLock);
            }
        }
        /* writerLock remains locked.  */
    }

    void writeUnLock()
    {
        LeaveCriticalSection(&writerLock);
    }

private:
    CRITICAL_SECTION countsLock;
    CRITICAL_SECTION writerLock;
    HANDLE noReaders;
    int readerCount;
    BOOL waitingWriter;
};

А вот как эта же красота могла бы выглядеть при использовании только Vista+ систем:
class RWLockSRW // For Windows Vista+ based on Slim RWLock
{
public:
    RWLockSRW()
        : srwLock()
    {
        InitializeSRWLock(&srwLock);
    }

    ~RWLockSRW()
    {
    }

    void readLock()
    {
        AcquireSRWLockShared(&srwLock);
    }

    void readUnLock()
    {
        ReleaseSRWLockShared(&srwLock);
    }

    void writeLock()
    {
        AcquireSRWLockExclusive(&srwLock);
    }

    void writeUnLock()
    {
        ReleaseSRWLockExclusive(&srwLock);
    }

private:
    RTL_SRWLOCK srwLock;
};

Мало того, что выглядит до безобразия просто, еще и работает на порядок быстрее. Но, есть одно но, как всегда… При попытке запустить приложение содержащее этот код (конечно мы умные ребята, сделали определение версии и для XP хотим использовать первый вариант, а для новых систем — второй), получим сообщение типа: «ой, а вот функции InitializeSRWLock что-то не нашлось в kernel32» после чего наше приложение любезно будет прибито.

Выход — грузить функции Slim RWLock динамически при помощи LoadLibrary, указателей на функции и этого всего:

typedef void(__stdcall *SRWLock_fptr)(PSRWLOCK);

class RWLockSRW // For Windows Vista+ based on Slim RWLock
{
public:
    RWLockSRW()
        : hGetProcIDDLL(NULL), 
        AcquireSRWLockShared_func(NULL),
        ReleaseSRWLockShared_func(NULL),
        AcquireSRWLockExclusive_func(NULL),
        ReleaseSRWLockExclusive_func(NULL),
        srwLock()
    {
        wchar_t path[MAX_PATH] = { 0 };
        GetSystemDirectory(path, sizeof(path));
        std::wstring dllPath = std::wstring(path) + L"\\kernel32.dll";
        HINSTANCE hGetProcIDDLL = LoadLibrary(dllPath.c_str());
        if (!hGetProcIDDLL)
        {
            throw std::exception("SRWLock Error loading kernel32.dll");
        }

        AcquireSRWLockShared_func = (SRWLock_fptr)GetProcAddress(hGetProcIDDLL, "AcquireSRWLockShared");
        if (!AcquireSRWLockShared_func)
        {
            throw std::exception("SRWLock Error loading AcquireSRWLockShared");
        }

        ReleaseSRWLockShared_func = (SRWLock_fptr)GetProcAddress(hGetProcIDDLL, "ReleaseSRWLockShared");
        if (!ReleaseSRWLockShared_func)
        {
            throw std::exception("SRWLock Error loading ReleaseSRWLockShared");
        }

        AcquireSRWLockExclusive_func = (SRWLock_fptr)GetProcAddress(hGetProcIDDLL, "AcquireSRWLockExclusive");
        if (!AcquireSRWLockExclusive_func)
        {
            throw std::exception("SRWLock Error loading AcquireSRWLockExclusive");
        }

        ReleaseSRWLockExclusive_func = (SRWLock_fptr)GetProcAddress(hGetProcIDDLL, "ReleaseSRWLockExclusive");
        if (!ReleaseSRWLockExclusive_func)
        {
            throw std::exception("SRWLock Error loading ReleaseSRWLockExclusive");
        }

        SRWLock_fptr InitializeSRWLock_func = (SRWLock_fptr)GetProcAddress(hGetProcIDDLL, "InitializeSRWLock");
        if (!InitializeSRWLock_func)
        {
            throw std::exception("SRWLock Error loading InitializeSRWLock");
        }

        InitializeSRWLock_func(&srwLock);
    }

    ~RWLockSRW()
    {
        if (hGetProcIDDLL)
        {
            FreeLibrary(hGetProcIDDLL);
        }
    }

    void readLock()
    {
        if (AcquireSRWLockShared_func)
        {
            AcquireSRWLockShared_func(&srwLock);
        }
    }

    void readUnLock()
    {
        if (ReleaseSRWLockShared_func)
        {
            ReleaseSRWLockShared_func(&srwLock);
        }
    }

    void writeLock()
    {
        if (AcquireSRWLockExclusive_func)
        {
            AcquireSRWLockExclusive_func(&srwLock);
        }
    }

    void writeUnLock()
    {
        if (ReleaseSRWLockExclusive_func)
        {
            ReleaseSRWLockExclusive_func(&srwLock);
        }
    }

private:
    HINSTANCE hGetProcIDDLL;

    SRWLock_fptr AcquireSRWLockShared_func;
    SRWLock_fptr ReleaseSRWLockShared_func;
    SRWLock_fptr AcquireSRWLockExclusive_func;
    SRWLock_fptr ReleaseSRWLockExclusive_func;

    RTL_SRWLOCK srwLock;
};

Выглядит кучерявей, зато стало портабельно. Осталось сделать враппер автоматически выбирающий нужный вариант в зависимости от версии Windows:
class RWLock // Wrapper
{
public:
     RWLock()
         : rwLockXP(NULL), rwLockSRW(NULL), isVistaPlus(IsWindowsVistaOrGreater())
     {
          if (isVistaPlus)
          {
               rwLockSRW = new RWLockSRW();
          }
          else
          {
               rwLockXP = new RWLockXP();
          }
     }

     ~RWLock()
     {
          if (isVistaPlus)
          {
               delete rwLockSRW;
          }
          else
          {
               delete rwLockXP;
          }
     }
     void readLock()
     {
          if (isVistaPlus)
          {
               rwLockSRW->readLock();
          }
          else
          {
               rwLockXP->readLock();
          }
     }
     void readUnLock()
     {
          if (isVistaPlus)
          {
               rwLockSRW->readUnLock();
          }
          else
          {
               rwLockXP->readUnLock();
          }
     }

     void writeLock()
     {
          if (isVistaPlus)
          {
               rwLockSRW->writeLock();
          }
          else
          {
               rwLockXP->writeLock();
          }
     }
     void writeUnLock()
     {
          if (isVistaPlus)
          {
               rwLockSRW->writeUnLock();
          }
          else
          {
               rwLockXP->writeUnLock();
          }
     }

private:
     RWLockXP  *rwLockXP;
     RWLockSRW *rwLockSRW;
     bool isVistaPlus;
};

И в завершении автолокер:
class ScopedRWLock
{
public:
    ScopedRWLock(RWLock *lc_, bool write_ = false)
        : lc(*lc_), write(write_)
    {
        if (write)
        {
            lc.writeLock();
        }
        else
        {
            lc.readLock();
        }
    }

    ~ScopedRWLock()
    {
        if (write)
        {
            lc.writeUnLock();
        }
        else
        {
            lc.readUnLock();
        }
    }
private:
    RWLock &lc;
    bool write;

    // Non copyable!
    static void *operator new(size_t);
    static void operator delete(void *);
    ScopedRWLock(const ScopedRWLock&);
    void operator=(const ScopedRWLock&);
};

Реализация с использованием pthread ничем не отличается от первой версии SRWLock за исключением других имен вызываемых функций.

Комментарии (0)

© Habrahabr.ru