Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1072932
  • 博文数量: 77
  • 博客积分: 11498
  • 博客等级: 上将
  • 技术积分: 1840
  • 用 户 组: 普通用户
  • 注册时间: 2006-05-04 11:10
文章分类

全部博文(77)

文章存档

2011年(1)

2010年(16)

2009年(5)

2008年(55)

分类: C/C++

2008-11-18 20:27:02


    实现一个Windows下的共享锁(读写锁)(一)
    作者:tyc611.cublog.cn,2008-11-18
在Windows Vista/Server 2008之前,Windows没有提供共享锁(通俗称为读写锁),只能靠自己实现。但从Windows Vista/Server 2008开始,Windows提供了用户态下的读写锁SRWLock,效率非常高。本文实现了一个简单的共享锁,可用于之前的Windows系统。

实现原理:
锁内部会记录当前锁类型、共享访问线程数和互斥访问线程数,并利用互斥量来保护这些内部数据,使用事件来实现共享访问线程和互斥访问线程的同步。

这里,使用了互斥量来保护数据,效率较低(因为必须进入内核态)。但如果使用临界区来进行保护,则无法保证离开临界区同时等待事件触发的原子性。有时间进一步研究是否可以使用临界区来代替这里的互斥量。

下面的类被命名为SharedMutex(可共享的互斥量),而不是SharedLock,原因是SharedMutex提供了共享锁的基本功能,可以直接使用。但为了使用方便,可以在此基础上包装出另一个更方便的共享锁SharedLock,这个在后续文章中给出实现。

类SharedMutex的源代码(文章后面附有打包下载):


/**
* SharedMutex.h
* @Author Tu Yongce
* @Created 2008-11-17
* @Modified 2008-11-17
* @Version 0.1
*/


#ifndef SHARED_MUTEX_H_INCLUDED
#define SHARED_MUTEX_H_INCLUDED

#ifndef _WIN32
#error "works only on Windows"
#endif

#include <windows.h>
#include "Noncopyable.h"

// 可共享的互斥量
class SharedMutex
    : private Noncopyable
{
private:
    HANDLE m_mutex;
    HANDLE m_sharedEvent;
    HANDLE m_exclusiveEvent;

    volatile int m_sharedNum;
    volatile int m_exclusiveNum;
    volatile int m_lockType;

    static const int LOCK_NONE = 0;
    static const int LOCK_SHARED = 1;
    static const int LOCK_EXCLUSIVE = 2;

public:
    SharedMutex();
    ~SharedMutex();

    // 获取共享访问权
    bool AcquireShared(DWORD waitTime = INFINITE);
    // 释放共享访问权
    void ReleaseShared();

    // 获取独占访问权
    bool AcquireExclusive(DWORD waitTime = INFINITE);
    // 释放独占访问权
    void ReleaseExclusive();
};

#endif // SHARED_MUTEX_H_INCLUDED


/**
* SharedMutex.cpp
* @Author Tu Yongce
* @Created 2008-11-17
* @Modified 2008-11-17
* @Version 0.1
*/


#include "SharedMutex.h"
#include <cassert>

SharedMutex::SharedMutex(): m_sharedNum(0), m_exclusiveNum(0), m_lockType(LOCK_NONE)
{
    // 创建用于保护内部数据的互斥量
    m_mutex = ::CreateMutex(NULL, FALSE, NULL);
    // 创建用于同步共享访问线程的事件(手动事件)
    m_sharedEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);
    // 创建用于同步独占访问线程的事件(自动事件)
    m_exclusiveEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);
}

SharedMutex::~SharedMutex()
{
    ::CloseHandle(m_mutex);
    ::CloseHandle(m_sharedEvent);
    ::CloseHandle(m_exclusiveEvent);
}

// 获取共享访问权
bool SharedMutex::AcquireShared(DWORD waitTime)
{
    ::WaitForSingleObject(m_mutex, INFINITE);
    ++m_sharedNum;
    if (m_lockType == LOCK_EXCLUSIVE) {
        DWORD retCode = ::SignalObjectAndWait(m_mutex, m_sharedEvent, waitTime, FALSE);
        if (retCode == WAIT_OBJECT_0) {
            return true;
        } else {
            if (retCode == WAIT_TIMEOUT)
                ::SetLastError(WAIT_TIMEOUT);
            return false;
        }
    }
    m_lockType = LOCK_SHARED;
    ::ReleaseMutex(m_mutex);
    return true;
}

// 释放共享访问权
void SharedMutex::ReleaseShared()
{
    assert(m_lockType == LOCK_SHARED);
    ::WaitForSingleObject(m_mutex, INFINITE);
    --m_sharedNum;
    if (m_sharedNum == 0) {
        if (m_exclusiveNum > 0) {
            // 唤醒一个独占访问线程
            m_lockType = LOCK_EXCLUSIVE;
            ::SetEvent(m_exclusiveEvent);
        } else {
            // 没有等待线程
            m_lockType = LOCK_NONE;
        }
    }
    ::ReleaseMutex(m_mutex);
}

// 获取独占访问权
bool SharedMutex::AcquireExclusive(DWORD waitTime)
{
    ::WaitForSingleObject(m_mutex, INFINITE);
    ++m_exclusiveNum;
    if (m_lockType != LOCK_NONE) {
        DWORD retCode = ::SignalObjectAndWait(m_mutex, m_exclusiveEvent, waitTime, FALSE);
        if (retCode == WAIT_OBJECT_0) {
            return true;
        } else {
            if (retCode == WAIT_TIMEOUT)
                ::SetLastError(WAIT_TIMEOUT);
            return false;
        }
    }
    m_lockType = LOCK_EXCLUSIVE;
    ::ReleaseMutex(m_mutex);
    return true;
}

// 释放独占访问权
void SharedMutex::ReleaseExclusive()
{
    assert(m_lockType == LOCK_EXCLUSIVE);
    ::WaitForSingleObject(m_mutex, INFINITE);
    --m_exclusiveNum;
    // 独占访问线程优先
    if (m_exclusiveNum > 0) {
        // 唤醒一个独占访问线程

        ::SetEvent(m_exclusiveEvent);
    } else if (m_sharedNum > 0) {
        // 唤醒当前所有共享访问线程
        m_lockType = LOCK_SHARED;
        ::PulseEvent(m_sharedEvent);
    } else {
        // 没有等待线程
        m_lockType = LOCK_NONE;
    }
    ::ReleaseMutex(m_mutex);
}



SharedMutex的测试代码:

/**
* SharedMutex_example.cpp
* @Author Tu Yongce
* @Created 2008-11-17
* @Modified 2008-11-17
* @Version 0.1
*/


#include <process.h>
#include <iostream>
#include "SharedMutex.h"

using namespace std;

SharedMutex g_mutex;

const int LOOP_NUM = 2000;
volatile __int64 g_data = 0;
CRITICAL_SECTION g_cs;

unsigned WINAPI ReaderThread(void *pParam)
{
    int id = (int)pParam;

    ::EnterCriticalSection(&g_cs);
    cout << "Reader [" << id << "] start" << endl;
    ::LeaveCriticalSection(&g_cs);

    __int64 max = 0;
    __int64 min = 0;

    for (int i = 0; i < LOOP_NUM; ++i) {
        g_mutex.AcquireShared();
        __int64 data = g_data;
        if (data > max)
            max = data;
        if (data < min)
            min = data;
        g_mutex.ReleaseShared();
        Sleep(1);
    }

    ::EnterCriticalSection(&g_cs);
    cout << "Reader [" << id << "] quit, max = " << max << ", min = " << min << endl;
    ::LeaveCriticalSection(&g_cs);

    return 0;
}

unsigned WINAPI WriterThread1(void *pParam)
{
    int id = (int)pParam;

    ::EnterCriticalSection(&g_cs);
    cout << "Writer1 [" << id << "] start" << endl;
    ::LeaveCriticalSection(&g_cs);

    for (int i = 0; i < LOOP_NUM; ++i) {
        g_mutex.AcquireExclusive();
        g_data = g_data + i;
        g_mutex.ReleaseExclusive();
        Sleep(1);
    }

    ::EnterCriticalSection(&g_cs);
    cout << "Writer1 [" << id << "] quit" << endl;
    ::LeaveCriticalSection(&g_cs);

    return 0;
}

unsigned WINAPI WriterThread2(void *pParam)
{
    int id = (int)pParam;

    ::EnterCriticalSection(&g_cs);
    cout << "Writer2 [" << id << "] start" << endl;
    ::LeaveCriticalSection(&g_cs);

    for (int i = 0; i < LOOP_NUM; ++i) {
        g_mutex.AcquireExclusive();
        g_data = g_data - i;
        g_mutex.ReleaseExclusive();
        Sleep(1);
    }

    ::EnterCriticalSection(&g_cs);
    cout << "Writer2 [" << id << "] quit" << endl;
    ::LeaveCriticalSection(&g_cs);

    return 0;
}

int main()
{
    ::InitializeCriticalSection(&g_cs);

    // 创建读写工作线程(创建时挂起工作线程)
    HANDLE readers[20];
    for (int i = 0; i < _countof(readers); ++i) {
        readers[i] = (HANDLE)_beginthreadex(NULL, 0, ReaderThread, (void*)i,
            CREATE_SUSPENDED, NULL);
    }

    HANDLE writers1[5];
    for (int i = 0; i < _countof(writers1); ++i) {
        writers1[i] = (HANDLE)_beginthreadex(NULL, 0, WriterThread1, (void*)i,
            CREATE_SUSPENDED, NULL);
    }

    HANDLE writers2[5];
    for (int i = 0; i < _countof(writers2); ++i) {
        writers2[i] = (HANDLE)_beginthreadex(NULL, 0, WriterThread2, (void*)i,
            CREATE_SUSPENDED, NULL);
    }

    // 恢复工作线程
    for (int i = 0; i < _countof(readers); ++i) {
        ResumeThread(readers[i]);
    }
    
    for (int i = 0; i < _countof(writers1); ++i) {
        ResumeThread(writers1[i]);
    }

    for (int i = 0; i < _countof(writers2); ++i) {
        ResumeThread(writers2[i]);
    }

    // 等待工作线程结束
    WaitForMultipleObjects(_countof(readers), readers, TRUE, INFINITE);
    WaitForMultipleObjects(_countof(writers1), writers1, TRUE, INFINITE);
    WaitForMultipleObjects(_countof(writers2), writers2, TRUE, INFINITE);

    // 释放内核对象句柄
    for (int i = 0; i < _countof(readers); ++i) {
        CloseHandle(readers[i]);
    }

    for (int i = 0; i < _countof(writers1); ++i) {
        CloseHandle(writers1[i]);
    }

    for (int i = 0; i < _countof(writers2); ++i) {
        CloseHandle(writers2[i]);
    }

    ::DeleteCriticalSection(&g_cs);

    cout << ">> Expected data value is " << 0 << ", and the real value is " << g_data << endl;

    return 0;
}



编译运行测试:
F:\tmp\SharedLock>cl SharedMutex.cpp SharedMutex_example.cpp /EHsc /Fettt.exe /n
ologo
SharedMutex.cpp
SharedMutex_example.cpp
正在生成代码...

F:\tmp\SharedLock>ttt.exe
Reader [0] start
Reader [1] start
Reader [2] start
Reader [3] start
Reader [4] start
Reader [5] start
Reader [6] start
Reader [7] start
Reader [8] start
Reader [9] start
Reader [10] start
Reader [11] start
Reader [12] start
Reader [13] start
Reader [14] start
Reader [15] start
Reader [16] start
Reader [17] start
Reader [18] start
Reader [19] start
Writer1 [0] start
Writer1 [1] start
Writer1 [2] start
Writer1 [3] start
Writer1 [4] start
Writer2 [0] start
Writer2 [1] start
Writer2 [2] start
Writer2 [3] start
Writer2 [4] start
Reader [3] quit, max = 8352, min = 0
Reader [2] quit, max = 8352, min = -1
Reader [1] quit, max = 9039, min = -3761
Reader [4] quit, max = 8352, min = 0
Reader [6] quit, max = 8352, min = -3453
Reader [8] quit, max = 9039, min = -1758
Reader [5] quit, max = 8352, min = -3453
Reader [0] quit, max = 8352, min = -1758
Reader [10] quit, max = 9460, min = -120
Reader [14] quit, max = 8352, min = 0
Reader [9] quit, max = 8352, min = -120
Reader [11] quit, max = 8352, min = -1758
Reader [12] quit, max = 8352, min = -1721
Reader [15] quit, max = 9460, min = 0
Reader [7] quit, max = 8352, min = -3453
Writer1 [4] quit
Reader [17] quit, max = 9460, min = -3453
Reader [13] quit, max = 8352, min = -3453
Reader [16] quit, max = 8352, min = -3453
Reader [19] quit, max = 8352, min = -120
Writer2 [4] quit
Reader [18] quit, max = 8352, min = -3453
Writer1 [1] quit
Writer2 [1] quit
Writer1 [3] quit
Writer2 [3] quit
Writer2 [0] quit
Writer1 [2] quit
Writer1 [0] quit
Writer2 [2] quit
>> Expected data value is 0, and the real value is 0

F:\tmp\SharedLock>

源代码打包下载

文件:SharedMutex.zip
大小:3KB
下载:下载


阅读(5783) | 评论(7) | 转发(0) |
给主人留下些什么吧!~~

cd_yangling2015-05-02 22:01:53

下面的代码可以证明 最新的MSDN https://msdn.microsoft.com/zh-cn/library/windows/desktop/ms686293%28v=vs.85%29.aspx remark提及的SOAW函数与PulseEvent可能的死锁

static DWORD WINAPI consumer(LPVOID args)
{
 

 for(;;) {
  counter++;

  WaitForSingleObject(hMutex, INFINITE);

  waiters++;

  SignalObjectAndWait(hMutex, hEvent, INFINITE, FALSE);

 }
}


static DWORD WINAPI producer(LPVOID args)

cd_yangling2015-05-02 22:00:55

从win7开始 SignalObjectAndWait 已经不再确保 是原子的动作,当然这对于自动事件没影响,但是对于PulseEvent和SignalObjectAndWait 影响很大。会导致信号丢失。
要想在win7 .2008 正确运行你的读写锁程序,必须把 读锁 (共享锁) 的HANDLE 更换成 信号量 .PulseEvent 采取 ReleaseSemaphore(rd_event, rd_counter, NULL);
代替。释放rd_counter个信号量值。初始化的时候,使用CreateSemaphore(NULL, 0, 0x7FFFFFFF, NULL); 信号量对于 SignalObjectAndWait的非原子操作不敏感,所以
可以移植到win7和2008下,为了兼容,其实一直都应该避免使用PulseEvent来唤醒多个线程,因为Semapore 的DOWN 操作也可以达到一样的目的。

chinaunix网友2011-06-01 15:25:08

AcquireShared() return false 时理论上是不应该调用ReleaseShared(),所以AcquireShared() return false 前要 --m_sharedNum; AcquireExclusive也有相同问题。

chinaunix网友2011-01-07 19:55:13

~~知道了。因为在 if (retCode == WAIT_OBJECT_0) 是在if (m_lockType != LOCK_NONE) 的条件下(也就是m_lockType = LOCK_SHARED条件),在ReleaseShared()中已经m_lockType = LOCK_EXCLUSIVE; ::SetEvent(m_exclusiveEvent); 了。。。。

chinaunix网友2011-01-06 21:41:31

我也有同样的以为。虽然SignalObjectAndWait()会ReleaseMutex(),但是 if (retCode == WAIT_OBJECT_0) { return true; //要在返回之前做 m_lockType = LOCK_SHARED; } else 。。。。 在返回之前 m_lockType = LOCK_SHARED; 应该设置一下吧?? 望解答呀