8.5 Slim读/写锁(SRWLock)——轻量级的读写锁

(1)SRWLock锁的目的

  ①允许读者线程同一时刻访问共享资源(因为不存在破坏数据的风险)

  ②写者线程应独占资源的访问权,任何其他线程(含写入的线程)要等这个写者线程访问完才能获得资源。

(2)SRWlock锁的使用方法

  

  ①初始化SRWLOCK结构体 InitializeSRWLock(PSRWLOCK pSRWLock);

  ②写者线程调用AcquireSRWLockExclusive(pSRWLock);以排它方式访问

     读者线程调用AcquireSRWLockShared以共享方式访问

  ③访问完毕后,写者线程调用ReleaseSRWLockExclusive解锁。读者线程要调用ReleaseSRWLockShared解锁

  ④注意SRWLock不需要删除和销毁,所以不用Delete之类的,系统会自动清理。

(3)SRWLock锁的共享规则

  ①若当前锁的状态是“写”(即某个线程已经获得排它锁),这时其他线程,不管是申请读或写锁的线程,都会被阻塞在AcquireSRWLock*函数中读锁或写锁等待计数加1。

  ②若当前锁的状态是“读”(即某个(些)线程已经获得了共享锁)。

  A、如果新的线程申请写锁,则此时它将被挂起,锁的写等待计数加1。直至当前正在读锁的线程全部结束,然后系统会唤醒正在等待写的线程,即申请排他锁要在没有任何其他锁的时候才能返回。

  B、如果新的线程申请读锁,若此时没有写线程正在等待,则允许读锁进入而不会被阻塞。如果有写锁正在等待,则写锁优先得到锁新线程进入等待,读锁计数加1(这样做的目的是让写锁有机会进入)。

(4)SRWLock与临界区的不同

  ①不存在TryEnter(Shared/Exclusive)SRWLock之类的函数;如果锁己经被占用,那么调用AcquireSRWLock(Shared/Exclusive)会阻塞调用线程。(如排它写入时,锁会被独占)(课本这说法已不适用了,在Win7以后系统提供了TryAcquireSRWLock(Exclusive/Shared)等函数,可以实现这需求了)

  ②不能递归获得SRWLock,即一个线程不能为了多次写入资源而多次锁定资源,再多次调用ReleaseSRWLock*来释放锁。

【SRWLock程序】

#include <windows.h>
#include <tchar.h>
#include <locale.h>
#include <time.h> //////////////////////////////////////////////////////////////////////////
const int g_iThreadCnt = ;
int g_iGlobalValue = ; //////////////////////////////////////////////////////////////////////////
SRWLOCK g_sl = { }; //////////////////////////////////////////////////////////////////////////
DWORD WINAPI ReadThread(PVOID pParam);
DWORD WINAPI WriteThread(PVOID pParam); //////////////////////////////////////////////////////////////////////////
int _tmain()
{
_tsetlocale(LC_ALL, _T("chs")); srand((unsigned int)time(NULL));
//读写锁只需初始化,不需要手动释放,系统会自行处理
InitializeSRWLock(&g_sl); HANDLE aThread[g_iThreadCnt];
DWORD dwThreadId = ;
SYSTEM_INFO si = { };
GetSystemInfo(&si); for (int i = ; i < g_iThreadCnt;i++){
if ( == rand()%)
aThread[i] = CreateThread(NULL, , (LPTHREAD_START_ROUTINE)WriteThread, NULL,
CREATE_SUSPENDED,&dwThreadId);
else
aThread[i] = CreateThread(NULL, , (LPTHREAD_START_ROUTINE)ReadThread, NULL,
CREATE_SUSPENDED, &dwThreadId);
SetThreadAffinityMask(aThread[i],<<(i % si.dwNumberOfProcessors));
ResumeThread(aThread[i]);
} //等待所有线程结束
WaitForMultipleObjects(g_iThreadCnt,aThread,TRUE,INFINITE); for (int i = ; i < g_iThreadCnt;i++){
CloseHandle(aThread[i]);
} _tsystem(_T("PAUSE"));
return ;
}
////////////////////////////////////////////////////////////////////////////
////不加保护的读写
//DWORD WINAPI ReadThread(PVOID pParam)
//{
// _tprintf(_T("Timestamp[%u]:Thread[ID:0x%x]读取全局变量值为%d\n"),
// GetTickCount(),GetCurrentThreadId(),g_iGlobalValue);
// return 0;
//}
//
////////////////////////////////////////////////////////////////////////////
//DWORD WINAPI WriteThread(PVOID pParam)
//{
//
// for (int i = 0; i <= 4321; i++){
// g_iGlobalValue = i;
// //模拟一个时间较长的处理过程
// for (int j = 0; j < 1000; j++);
// }
//
// //我们的要求是写入的最后数值应该为4321,全局变量未被保护
// //其中线程可能读取0-4321的中间值,这不是我们想要的结果!
// _tprintf(_T("Timestamp[%u]:Thread[ID:0x%x]写入数据值为%d\n"),
// GetTickCount(), GetCurrentThreadId(), g_iGlobalValue);
// return 0;
//} //////////////////////////////////////////////////////////////////////////
DWORD WINAPI ReadThread(PVOID pParam)
{
//以共享的访问读
__try{
AcquireSRWLockShared(&g_sl); //读出来的全局变量要么是0,要么是4321。不可能有其他值
//当读线程第1个被调度时,会读到0.但一旦写线程被调度,以后所有的
//读取的值都会是4321
_tprintf(_T("Timestamp[%u]:Thread[ID:0x%X]读取全局变量值为%d\n"),
GetTickCount(), GetCurrentThreadId(), g_iGlobalValue);
}
__finally{
ReleaseSRWLockShared(&g_sl);
} return ;
} //////////////////////////////////////////////////////////////////////////
DWORD WINAPI WriteThread(PVOID pParam)
{
//写时以排它的方式
__try{
AcquireSRWLockExclusive(&g_sl);
for (int i = ; i <= ; i++){
g_iGlobalValue = i;
SwitchToThread(); //故意切换到其他线程,很明显
//在这个循环的期间,因排它方式
//所以其它访问锁的线程会被挂起
//从而无法读或写。
} //我们的要求是写入的最后数值应该为4321,全局变量未被保护
//其中线程可能读取0-4321的中间值,这不是我们想要的结果!
_tprintf(_T("Timestamp[%u]:Thread[ID:0x%X]写入数据值为%d\n"),
GetTickCount(), GetCurrentThreadId(), g_iGlobalValue);
}
__finally{
ReleaseSRWLockExclusive(&g_sl);
} return ;
}

(5)SRWLock锁与其他锁的比较——实验:对同一个全局变量加不同类型锁,同时让每个线程进行1000000次读写性能的比较(时间单位为ms

线程数

Volatile

读取

Volatile

写入

InterLocked

递增

临界区

SRWLock

共享

SRWLock

排它

互斥量

Mutex

1

39

42

57

84

86

88

1098

2

42

54

80

126

150

120

5774

4

95

119

161

283

236

236

11589

  ①InterLockedIncrement比Volatile读写慢是因为CPU必须锁定内存,但InterLocked函数只能同步一些简单的整型变量。

  ②临界区与SRWLock锁效率差不多,但建议用SRWLock代替临界区,因为该锁允许多个线程同时读取,对那些只需要读取共享资源线程来说,这提高了吞吐量

  ③内核对象的性能最差,这是因为等待、释放互斥量需要在用户模式与内核模式之间切换。

  ④如果考虑性能,首先应该尝试不要共享数据,然后依次是Volatile读写、InterLock函数、SRWLock锁、临界区。当这些条件都不满足时,再使用内核对象。

【UserSyncCompare程序】比较不同类型锁的性能

/***********************************************************************
Module: UserSyncCompare.cpp
Notices: Copyright(c) 2008 Jeffrey Richter & Christophe Nasarre
***********************************************************************/ #include "../../CommonFiles/CmnHdr.h"
#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <locale.h> //////////////////////////////////////////////////////////////////////////
// 晶振计时类,基于主板晶振频率的时间戳计数器的时间类(见第7章)
class CStopWatch {
public:
CStopWatch() { QueryPerformanceFrequency(&m_liPerfFreq); Start(); } void Start() { QueryPerformanceCounter(&m_liPerfStart); } //返回计算自调用Start()函数以来的毫秒数
__int64 Now() const {
LARGE_INTEGER liPerfNow;
QueryPerformanceCounter(&liPerfNow);
return(((liPerfNow.QuadPart - m_liPerfStart.QuadPart) * )
/ m_liPerfFreq.QuadPart);
} private:
LARGE_INTEGER m_liPerfFreq; // Counts per second
LARGE_INTEGER m_liPerfStart; // Starting count
}; //////////////////////////////////////////////////////////////////////////
DWORD g_nIterations = ; //叠代次数
typedef void(CALLBACK* OPERATIONFUNC)(); DWORD WINAPI ThreadInterationFunction(PVOID operationFunc){
OPERATIONFUNC op = (OPERATIONFUNC)operationFunc; for (DWORD iteration = ; iteration < g_nIterations;iteration++){
op();
}
return ;
}
//////////////////////////////////////////////////////////////////////////
//MeasureConcurrentOperation函数
//功能:测试一组并行线程的性能
//参数:szOperationName——操作的名称
// nThreads——测试线程的数量
// ofnOperationFunc——具体以哪种方式读、写操作的函数
void MeasureConcurrentOperation(TCHAR* szOperationName,DWORD nThreads,
OPERATIONFUNC opfOperationFunc)
{
HANDLE* phThreads = new HANDLE[nThreads];
//将当前线程的优先级提到“高”
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
for (DWORD i = ; i < nThreads;i++){
phThreads[i] = CreateThread(NULL, ,
ThreadInterationFunction, //线程函数
opfOperationFunc,//线程函数的参数(是个函数指针)
, //立即运行
NULL);
}
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL);
CStopWatch watch; //等待所有线程结束
WaitForMultipleObjects(nThreads, phThreads, TRUE, INFINITE); __int64 elapsedTime = watch.Now(); _tprintf(_T("线程数=%u,毫秒=%u,测试类型=%s\n"),
nThreads,(DWORD)elapsedTime,szOperationName); //别忘了关闭所有的线程句柄,并删除线程数组
for (DWORD i = ; i < nThreads; i++){
CloseHandle(phThreads[i]);
}
delete phThreads;
} //////////////////////////////////////////////////////////////////////////
//测试列表:
//1、不加同步锁,对整型volatile变量直接读取
//2、使用InterlockedIncrement对整型变量进行写操作
//3、使用临界区对整型volatile变量进行读取
//4、使用SRWLock锁对整型volatile变量进行读写操作
//5、使用互斥对象Mutex对整型volatile变量进行读取操作 volatile LONG gv_value = ; //1、直接读写volatile型的整型变量
//'lValue':是个局部变量,己初始化,但未被引用,编译器
//会出现警告,可禁用该警告
#pragma warning(disable:4189)
void WINAPI VolatileReadCallBack()
{
LONG lValue = gv_value;
}
#pragma warning(default:4189) void WINAPI VolatileWriteCallBack()
{
gv_value = ;
} //2、使用InterlockedIncrement对整型变量进行写操作
void WINAPI InterlockedIncrementCallBack()
{
InterlockedIncrement(&gv_value);
} //3、使用临界区对整型volatile变量进行读取
CRITICAL_SECTION g_cs;
void WINAPI CriticalSectionCallBack()
{
EnterCriticalSection(&g_cs);
gv_value = ;
LeaveCriticalSection(&g_cs);
} //4、使用SRWLock锁对整型volatile变量进行读写操作
SRWLOCK g_srwLock;
void WINAPI SRWLockReadCallBack()
{
AcquireSRWLockShared(&g_srwLock);
gv_value = ;
ReleaseSRWLockShared(&g_srwLock);
} void WINAPI SRWLockWriteCallBack()
{
AcquireSRWLockExclusive(&g_srwLock);
gv_value = ;
ReleaseSRWLockExclusive(&g_srwLock);
} //5、使用互斥对象Mutex对整型volatile变量进行读取操作
HANDLE g_hMutex;
void WINAPI MutexCallBack()
{
WaitForSingleObject(g_hMutex, INFINITE);
gv_value = ;
ReleaseMutex(g_hMutex);
} //////////////////////////////////////////////////////////////////////////
int _tmain()
{
_tsetlocale(LC_ALL, _T("chs")); //分别测试当线程总数为1、2、4时各种锁切换所花费的时间
for (int nThreads = ; nThreads <= ;nThreads *= ){
//1、直接读写
MeasureConcurrentOperation(_T("Volatile Read"), nThreads, VolatileReadCallBack);
MeasureConcurrentOperation(_T("Volatile Write"), nThreads, VolatileWriteCallBack); //2、InterlockedIncrement
MeasureConcurrentOperation(_T("Interlocked Increment"), nThreads, InterlockedIncrementCallBack); //3、临界区:
InitializeCriticalSection(&g_cs);//初始化临界区
MeasureConcurrentOperation(_T("Critical Section"), nThreads, CriticalSectionCallBack);
DeleteCriticalSection(&g_cs); //4、SRWLock锁:
InitializeSRWLock(&g_srwLock);//初始化SRWLock锁,注意不需要释放,系统会自行回收
MeasureConcurrentOperation(_T("SRWLock Read"), nThreads, SRWLockReadCallBack);
MeasureConcurrentOperation(_T("SRWLock Write"), nThreads, SRWLockWriteCallBack); //5、互斥对象:
g_hMutex = CreateMutex(NULL, false, NULL);//准备互斥对象
MeasureConcurrentOperation(_T("Mutex"), nThreads, MutexCallBack);
CloseHandle(g_hMutex);
_tprintf(_T("\n"));
}
return ;
}

第8章 用户模式下的线程同步(3)_Slim读写锁(SRWLock)的更多相关文章

  1. Windows核心编程:第8章 用户模式下的线程同步

    Github https://github.com/gongluck/Windows-Core-Program.git //第8章 用户模式下的线程同步.cpp: 定义应用程序的入口点. // #in ...

  2. windows核心编程---第七章 用户模式下的线程同步

    用户模式下的线程同步 系统中的线程必须访问系统资源,如堆.串口.文件.窗口以及其他资源.如果一个线程独占了对某个资源的访问,其他线程就无法完成工作.我们也必须限制线程在任何时刻都能访问任何资源.比如在 ...

  3. 第8章 用户模式下的线程同步(4)_条件变量(Condition Variable)

    8.6 条件变量(Condition Variables)——可利用临界区或SRWLock锁来实现 8.6.1 条件变量的使用 (1)条件变量机制就是为了简化 “生产者-消费者”问题而设计的一种线程同 ...

  4. 第8章 用户模式下的线程同步(1)_Interlocked系列函数

    8.1 原子访问:Interlocked系列函数(Interlock英文为互锁的意思) (1)原子访问的原理 ①原子访问:指的是一线程在访问某个资源的同时,能够保证没有其他线程会在同一时刻访问该资源. ...

  5. 第8章 用户模式下的线程同步(2)_临界区(CRITICAL_SECTION)

    8.4 关键段(临界区)——内部也是使用Interlocked函数来实现的! 8.4.1 关键段的细节 (1)CRITICAL_SECTION的使用方法 ①CRITICAL_SECTION cs;   ...

  6. 《windows核心编程系列》七谈谈用户模式下的线程同步

    用户模式下的线程同步 系统中的线程必须访问系统资源,如堆.串口.文件.窗口以及其他资源.如果一个线程独占了对某个资源的访问,其他线程就无法完成工作.我们也必须限制线程在任何时刻都能访问任何资源.比如在 ...

  7. 【windows核心编程】 第八章 用户模式下的线程同步

    Windows核心编程 第八章 用户模式下的线程同步 1. 线程之间通信发生在以下两种情况: ①    需要让多个线程同时访问一个共享资源,同时不能破坏资源的完整性 ②    一个线程需要通知其他线程 ...

  8. 用户模式下的线程同步的分析(Windows核心编程)

    线程同步 同一进程或者同一线程可以生成许多不同的子线程来完成规定的任务,但是多个线程同时运行的情况下可能需要对某个资源进行读写访问,比如以下这个情况:创建两个线程对同一资源进行访问,最后打印出这个资源 ...

  9. 《Windows核心编程》第八章——用户模式下的线程同步

    下面起了两个线程,每个对一个全局变量加500次,不假思索进行回答,会认为最后这个全局变量的值会是1000,然而事实并不是这样: #include<iostream> #include &l ...

随机推荐

  1. 每日一博 | 用 Ionic2 创建 App 启动页滑动欢迎界面

    原文  https://my.oschina.net/qinphil/blog/777787 效果如下,图片来自网络 本文例子和上图稍有不同,主要功能如下: 每滑动一下展示一张全屏图片: 滑动到最后一 ...

  2. 如何查找SAP的事务代码清单

    SAP系统中,为了省去输入程序名称等繁琐步骤,SAP提供一种命令,称作‘事务代码’,通过执行事务代码达到快速进入相应程序的目的.那么在系统中如何去查找事务代码,事务代码和程序的对应关系如何呢?我们可以 ...

  3. atitit.userService 用户系统设计 v4 q316 .doc

    atitit.userService 用户系统设计 v4 q316 .doc 1. 新特性1 2. Admin  login1 3. 用户注册登录2 3.1. <!-- 会员注册使用 --> ...

  4. Android Tips: 打电话和发短信

    利用Android打电话非常简单,直接调用Android内在的电话功能就可以了. btnDail.setOnClickListener(new OnClickListener(){ @Override ...

  5. JavaScript学习04 对象

    JavaScript学习04 对象 默认对象 日期对象Date, 格式:日期对象名称=new Date([日期参数]) 日期参数: 1.省略(最常用): 2.英文-数值格式:月 日,公元年 [时:分: ...

  6. 《The Linux Command Line》 读书笔记04 Linux用户以及权限相关命令

    Linux用户以及权限相关命令 查看身份 id:Display user identity. 这个命令的输出会显示uid,gid和用户所属的组. uid即user ID,这是账户创建时被赋予的. gi ...

  7. SQLite Design and Concepts

    API 分为两大类 core API. 基本的SQL操作 extension API. 创建自定义的SQL操作. 基本数据结构 需要了解的组成部分有连接.statments.B树.pager. 为了写 ...

  8. 极其简单的搭建eclipse的android开发环境

    这篇博客是关于如何搭建eclipse的android开发环境, 与网上的其他博客不同,我的方法比他们简单的多,所 以推荐给大家. 搭建eclipse的android开发环境步骤: 1.配置JDK(Ja ...

  9. 【代码笔记】iOS-仿安卓,本页出现多个选择项

    一,效果图. 二,代码. //点击任何处,弹出提示选项 -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ UIAlert ...

  10. Java ThreadLocal

    ThreadLocal类,代表一个线程局部变量,通过把数据放在ThreadLocal中,可以让每个线程创建一个该变量的副本.也可以看成是线程同步的另一种方式吧,通过为每个线程创建一个变量的线程本地副本 ...