读写锁 share_mutex

2023-11-11

实现一个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
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

读写锁 share_mutex 的相关文章

  • 如何确定位置计数器“.”的基地址。 VMA 和 LMA 何时不同?

    根据ld手册在特殊符号上 即位置计数器 笔记 实际上指的是从开始的字节偏移量 当前包含对象 通常这是 SECTIONS 语句 其起始地址为 0 因此 可以用作绝对地址 如果 然而 它是在部分描述中使用的 它指的是 从该部分开始的字节偏移量
  • 如何使用__malloc_hook?

    In GNU C 库参考手册 有一个示例程序 p 65 但我不知道这三句话是什么 malloc hook old malloc hook old malloc hook malloc hook malloc hook my malloc h
  • 为什么 GLibC 中使用“不间断空格”和其他 ispunct() ?

    我一直在看
  • GDB 单步执行动态链接器(ld.so) 代码

    我想在我的正常使用中使用 ld so 的代码c代码 我正在尝试通过 GDB 中的代码流TUI当您单步执行代码时 您可以看到源代码和汇编代码 为此我还安装了libc dbg binutils source来自 ubuntu 包管理器的包 GD
  • 如何包装 glibc 库函数以自动使用“unsigned char”和“wchar_t”?

    我总是用char as unsigned char and wchar t as wint t 鉴于此 是否存在一种解决方案来更改函数接口以使用这些类型 无论是否重新编译 glibc 另请参阅这个问题 如何更改 wchar h 以使 wch
  • 如何使用汇编制作小型二进制文件?

    我正在为我的一些项目编写一些汇编代码 我看到了一些有趣的东西 链接时二进制文件的大小非常大 所以我测试了又测试 即使使用尽可能少的代码行 输出的 Elf 二进制文件也很大 例如 section text global start start
  • Rocksdb.errors.RocksIOError:IO错误:锁定文件时:sample.db/LOCK:资源暂时不可用

    如何删除rocksDB上的锁 我尝试运行以下代码但出现以下错误 Running on http 127 0 0 1 5000 Press CTRL C to quit Restarting with stat Traceback most
  • Conda报告有冲突,但似乎没有冲突

    我一直在努力更新pyqt到最新版本 但由于包冲突而失败 我尝试更新的包在这里似乎无关紧要 从我最后附上的以下消息可以明显看出 glibc已安装版本 2 35 所有包都需要glibc2 17 或更高版本 所以没有冲突 但是拒绝安装 那么 这个
  • 单个主机上的多个 glibc 库

    我的 Linux SLES 8 服务器当前有 glibc 2 2 5 235 但我有一个程序无法在此版本上运行并且需要 glibc 2 3 3 是否可以在同一主机上安装多个 glibc 这是我在旧 glibc 上运行程序时遇到的错误 mya
  • Glibc - ucontext.h 中出现错误,但仅适用于 -std=c11

    我有这个最小的 helloworld 扩展包括ucontext h include
  • 隐藏链接到 .so 文件的第 3 方 .a 文件中的符号

    我正在构建一个共享 so 库 该库由多个 a 文件和调用它们的瘦 API 层组成 我只希望我的 API 和外部依赖项可见 因此我使用 GCC 提供的 隐藏 可见性构建代码 fvisibility hidden 然而 其中一个库是专有的第三方
  • 为什么标准输出不能被替换?

    出于教育目的 我尝试替换标准流 stdout stdin 和 stderr 我首先查找流的数据类型 我追溯到具有以下成员的 struct IO FILE gdb ptype IO FILE type struct IO FILE int f
  • 使用 UCLIBC 交叉编译 PHP

    这是一个转发 之前的帖子已关闭 移至 SERVERFAULT 并再次关闭 我认为这篇文章是一个有效的堆栈溢出问题 因为我认为它是由一些 automake 编译 链接错误引起的 这是一个编程问题而不是服务器管理问题 我会检查 uClibc 的
  • 如何用IPC::Open2过滤大量数据?

    我的任务是使用外部实用程序 addr2line 从 perl 脚本中过滤一些数据 数据量相当大 我需要打印大量数据stdin程序并读回大量数据 来自stdout程序到我的脚本中 现在我这样做IPC Open2 但我不混合阅读和写作 这合法吗
  • 使用 glibc 而不是默认库编译的 C 程序:执行时权限被拒绝

    这是我在 stackoverflow 上的第一个问题 所以我会尽力做好 Context 我想提供一个可以在每个 Linux 发行版上运行的程序 例如 一个将使用 C 11 的程序 在没有 C 11 库的系统上运行 为此 我想复制我的程序使用
  • 将键值数据库与 Spark 集成

    我无法理解 Spark 如何与存储交互 我想创建一个从 RocksDB 数据库 或任何其他键值存储 获取数据的 Spark 集群 然而 此时此刻 我能做的最好的事情就是将整个数据集从数据库获取到每个集群节点的内存中 例如映射到映射中 并从该
  • ROCKSDB 由于rocksdb_max_row_locks 无法获取锁

    我尝试将 CSV 加载到 Rocksdb 数据库中 但失败并显示以下错误 Got error 10 Operation aborted Failed to acquire lock due to rocksdb max row locks
  • 使用动态链接器包装 glibc 函数

    我正在尝试包装 GLIBCfstat通过将我的库注入可执行文件来实现函数 可以是任何其他 这只是概念证明 我通过将我的库放置在可执行文件的位置来做到这一点RPATH用名字指着libc so 6 我的库的源代码如下 define GNU SO
  • Macports 安装 binutils

    我正在尝试通过 macports 在 Lion 上安装 gnu ld 我已经从 macports 安装了 gcc 4 6 但是我需要创建 elf 二进制形式的二进制文件 我作为单独的问题发布here https stackoverflow
  • 如何查看 Ubuntu 上的 Binutils 版本?

    我已经搜索过 但没有找到一个选项来告诉我我的 Ubuntu 上有哪个版本的 binutils 或者至少我不知道如何解释它 gcc v 没有透露任何有关 binutils 的信息 ld v 告诉我GNU ld GNU Binutils for

随机推荐

  • mysql MHA集群安装

    一 主机规划 IP Hostname Master Slave Manager Node Data Node 10 22 83 42 node1 Master Data Node 10 22 83 26 node2 Slave Data N
  • 如何使用Egret制作游戏?

    好的 下面是使用Egret制作游戏的详细教程 一 前期准备 1 安装Egret Wing开发环境 可以在官网下载 https www egret com products wing html 2 安装Egret Engine 可以在官网下载
  • BES2300Z USB mode 讲解

    hello 在BES的蓝牙中有一些芯片是支持USB mode 在使用的过程中 在BT mode 和 USB mode 中只能有一种模式存在 排版会有点乱 请谅解 下面来讲解下BES2300Z 在USB mode 下打开的方法 遇到的一些问题
  • robotframework安装与详解

    Robot Framework 以下简称rf 是一款python编写的功能自动化测试框架 具备良好的可扩展性 支持关键字驱动 可以同时测试多种类型的客户端或者接口 可以进行分布式测试执行 主要用于轮次很多的验收测试和验收测试驱动开发 ATD
  • Python requests ip代理爬虫报错 HTTPSConnectionPool(host=‘xxxxx‘, port=443) Max retries exceed

    本人系统 macOS10 15 6 Catalina 场景 使用Python requests 包 ip代理池爬取网站数据 出现报错 HTTPSConnectionPool host xxxxx port 443 Max retries e
  • JDBC实现

    JDBC编程步骤如下 1 Load the Driver 加载驱动 1 注冊驱动有三种方式 1 Class forName com mysql jdbc Driver 推荐这样的方式 不会对详细的驱动类产生依赖 2 DriverManage
  • 菜鸟学习篇--Vuecli4.0 Vant ui组件样式无效果

    新建了个vue项目 按需引入vant组件的时候 发现页面不出样式效果 解决办法是 第一步 在bable config js文件中加入 plugins import libraryName vant libraryDirectory es s
  • git需要掌握的基础知识

    Git的简介 Git 是一款免费的 开源的 分布式的版本控制系统 旨在快速高效地处理无论规模大小的任何软件工程 每一个 Git克隆 都是一个完整的文件库 含有全部历史记录和修订追踪能力 不依赖于网络连接或中心服务器 其最大特色就是 分支 及
  • 7 个隐藏的 Blender 技巧将改善您的工作流程

    谁不喜欢秘密技巧 因为 Blender 是一个全面的 多功能的工具 所以有很多隐藏的复活节彩蛋 隐藏在可见表面之下的时尚工具和功能 对于今天的文章中 让我们来找出了最好的秘诀Blender技巧以提高您的工作流程与效率 1 轻松选择集合中的所
  • Spring + MyBatis

    MyBatis ORM 对象 关系映射 完成对象数据到关系型数据映射的机制称为对象 关系映射 1 MyBatis是一个ORM框架 也是一个持久层框架 MyBatis封装了JDBC 将数据库中的表数据自动封装到对象中 这样就可以以面向对象的方
  • 快手小店怎么引流?快手怎么做店铺引流?

    短视频的内容丰富 更能吸引广大用户 因为它可以让用户得到视觉的享受等等 就像快手平台 以短视频迅速攻占用户的心 尤其是三四线城市的用户 非常喜欢在快手上分享自己的生活和见闻 现在就连淘宝也想借助快手平台为自己的店铺进行引流 快速实现产品的变
  • IDEA常用插件之注解插件

    文章目录 注解插件 JavaDoc插件 安装 修改配置 生成文档加入自己信息 Easy JavaDoc 安装插件 在线安装 离线安装 中文名自动转英文 加注释 默认快捷键 可通过IDEA快捷键设置修改 注解插件 JavaDoc插件 安装 修
  • 数据库SQL语句期末总复习

    文章目录 SQL的分类 DDL数据定义语言 数据库的定义与撤销 基本表的定义与维护 索引的建立与删除 DQL数据查询语言 单表查询 查询结果排序 分组查询 连接查询 嵌套查询 集合查询 DML数据操作语言 插入数据 更新数据 删除数据 视图
  • 创建HttpPost和HttpGet请求

    1 创建工具类 HttpClient import com alibaba fastjson JSON import org apache http HttpStatus import org apache http client conf
  • react 函数组件父组件调用子组件方法

    react 函数组件父组件调用子组件方法 父组件利用ref对子组件做标记 通过调用子组件方法更改子组件状态 也可以调用子组件方法 首先在父组件中 使用useRef创建一个ref import LogModal from logModal i
  • vue单选框选中_vue radio单选框,获取当前项(每一项)的value值操作

    前言 本文使用了lable关联选中 实际使用中如果不需要 直接将循环语句 v for 写在 input标签上就可以 1 使用v for循环的radio单选框 01 需要注意的是 这是使用的是 change 事件 而不是 click 点击事件
  • NSSCTF刷题

    web NSSCTF 2022 Spring Recruit babyphp
  • UEFI基本概念

    TianoCore UEFI EDK2 UEFI Unified Extensible Firmware Interface 用来取代BIOS TianoCore 一个社区 支持UEFI的开源实现 EDK2 一种UEFI的开发环境 UEFI
  • AI大模型有哪些?国内的

    今年相信大家都被ChatGPT刷过屏 因为它太好用了 问它一个问题 它就能回答 可以帮助我们写各种文字 甚至写代码 对于我们的工作有着很大的帮助 国内这半年对于AI这个行业也出现了很多的公司以及产品概念 各家大厂也在抓紧研发 和GPT一样的
  • 读写锁 share_mutex

    实现一个Windows下的共享锁 读写锁 一 作者 tyc611 cublog cn 2008 11 18 在Windows Vista Server 2008之前 Windows没有提供共享锁 通俗称为读写锁 只能靠自己实现 但从Wind