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. 【iScroll源码学习04】分离IScroll核心

    前言 最近几天我们前前后后基本将iScroll源码学的七七八八了,文章中未涉及的各位就要自己去看了 1. [iScroll源码学习03]iScroll事件机制与滚动条的实现 2. [iScroll源码 ...

  2. js动态计算移动端rem

    在做移动端web app的时候,众所周知,移动设备分辨率五花八门,虽然我们可以通过CSS3的media query来实现适配,例如下面这样: html { font-size : 20px; } @m ...

  3. crm2013关于contentIFrame不能使用

    在CRM2011里面,我们可以在页面的控制台里面输入: contentIFrame.Xrm.Page.data.entity.getEntityName(); contentIFrame.Xrm.Pa ...

  4. 一维Poisson方程计算

    package com.smartmap.algorithm.equation.differential.partial.ellipsoidal; import java.io.FileOutputS ...

  5. Android 6 检查权限代码

    private static final int MY_PERMISSIONS_REQUEST_READ_CONTACTS= 0; //检查目前是否有权限 if (checkSelfPermissio ...

  6. Presenting view controllers on detached view controllers is discouraged <CallViewController: 0x14676e240>.

    今天在优化app时,发现程序出现这种警告:“ Presenting view controllers on detached view controllers is discouraged <C ...

  7. jquery.validate 验证机制

    jquery.validate 验证机制 金刚 juqery juqery.validate 在开发系统时,使用了jquery.validate.js 这个验证插件,来校验数据合法性 重点 验证是以i ...

  8. 跨域iframe的高度自适应

    If you cannot hear the sound of the genuine in you, you will all of your life spend your days on the ...

  9. redmine + apache + mod_fcgid

    redmine默认是用webrick启动的,这种方法不适合生产环境,最好部署在apache下,本文介绍如何通过mod_fcgid启动. 首先要有一个能够启动的redmine,可以通过我之前的博文查看: ...

  10. ORACLE 查看有多个执行计划的SQL语句

    在SQL优化过程,有时候需要查看哪些SQL具有多个执行计划(Multiple Executions Plans for the same SQL statement),因为同一个SQL有多个执行计划一 ...