第8章 用户模式下的线程同步(3)_Slim读写锁(SRWLock)
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)的更多相关文章
- Windows核心编程:第8章 用户模式下的线程同步
Github https://github.com/gongluck/Windows-Core-Program.git //第8章 用户模式下的线程同步.cpp: 定义应用程序的入口点. // #in ...
- windows核心编程---第七章 用户模式下的线程同步
用户模式下的线程同步 系统中的线程必须访问系统资源,如堆.串口.文件.窗口以及其他资源.如果一个线程独占了对某个资源的访问,其他线程就无法完成工作.我们也必须限制线程在任何时刻都能访问任何资源.比如在 ...
- 第8章 用户模式下的线程同步(4)_条件变量(Condition Variable)
8.6 条件变量(Condition Variables)——可利用临界区或SRWLock锁来实现 8.6.1 条件变量的使用 (1)条件变量机制就是为了简化 “生产者-消费者”问题而设计的一种线程同 ...
- 第8章 用户模式下的线程同步(1)_Interlocked系列函数
8.1 原子访问:Interlocked系列函数(Interlock英文为互锁的意思) (1)原子访问的原理 ①原子访问:指的是一线程在访问某个资源的同时,能够保证没有其他线程会在同一时刻访问该资源. ...
- 第8章 用户模式下的线程同步(2)_临界区(CRITICAL_SECTION)
8.4 关键段(临界区)——内部也是使用Interlocked函数来实现的! 8.4.1 关键段的细节 (1)CRITICAL_SECTION的使用方法 ①CRITICAL_SECTION cs; ...
- 《windows核心编程系列》七谈谈用户模式下的线程同步
用户模式下的线程同步 系统中的线程必须访问系统资源,如堆.串口.文件.窗口以及其他资源.如果一个线程独占了对某个资源的访问,其他线程就无法完成工作.我们也必须限制线程在任何时刻都能访问任何资源.比如在 ...
- 【windows核心编程】 第八章 用户模式下的线程同步
Windows核心编程 第八章 用户模式下的线程同步 1. 线程之间通信发生在以下两种情况: ① 需要让多个线程同时访问一个共享资源,同时不能破坏资源的完整性 ② 一个线程需要通知其他线程 ...
- 用户模式下的线程同步的分析(Windows核心编程)
线程同步 同一进程或者同一线程可以生成许多不同的子线程来完成规定的任务,但是多个线程同时运行的情况下可能需要对某个资源进行读写访问,比如以下这个情况:创建两个线程对同一资源进行访问,最后打印出这个资源 ...
- 《Windows核心编程》第八章——用户模式下的线程同步
下面起了两个线程,每个对一个全局变量加500次,不假思索进行回答,会认为最后这个全局变量的值会是1000,然而事实并不是这样: #include<iostream> #include &l ...
随机推荐
- 为什么URL中的中文需要Encode两次?
在URL中传参的时候常常需要传入中文,这个时候就需要对中文参数进行编码,即URLEncode.但是,常常是Encode两次,而不是一次,为什么呢? 首先要知道,tomcat会自动解码一次: 这样的话, ...
- easyui日期在未加载easyui-lang-zh_CN.js出现英文的情况下加载中文的方法
我们有时候在操作easyui的时候本来是加载了easyui-lang-zh_CN.js中文文件包,但是还是出现了英文.使得我们不得埋怨这框架咋这么不好用,其实我们仔细看看这个中文包就会发现里面很多都是 ...
- Weinre调试移动端页面
Weinre是什么 如果我们做的是Cordova(phonegap)或其他hybird应用,当使用到原生功能时候(类似原生请求数据或页面切换时),没办法在PC chrome浏览器调试页面,一旦页面在手 ...
- ARP投毒及其防御方法
1.攻击原理 ARP欺骗就是中间人欺骗pc机,告诉pc机它是服务器.再欺骗服务器,告诉服务器它就是pc机.以致获取服务器与pc机的会话信息. 中间人欺骗服务器时,会给服务器发一个报文,发之前把报文中的 ...
- Office 365 - SharePoint 2013 Online之添加App开发工具Napa
1.新建一个网站集,模板选择开发人员模板,如下图: 2.确定以后,需要稍等一会儿; 3.点击网站内容,添加app,如下图: 4.进入SharePoint Store,选择Napa,如下图: 5.选择A ...
- 115个Java面试题和答案——终极列表(下)
第一篇讨论了面向对象编程和它的特点,关于Java和它的功能的常见问题,Java的集合类,垃圾收集器,本章主要讨论异常处理,Java小应用程序,Swing,JDBC,远程方法调用(RMI),Servle ...
- iOS UITableViewController出现crash
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to de ...
- IOS开发之代理的设计小技巧
1.关于代理对象的设计小技巧 在设计一个类,需要通过代理和协议来从外部获取需要的动态的数据.那么在这里设计使用代理会有两种方法. <第一种方法> 也是比较常见的: 在你设计的类中,声明一个 ...
- HTML5气泡悬浮框(已经加上完整文件)
源文件链接:http://pan.baidu.com/s/1pKHlNSn 设计气泡悬浮框 1.在网页设计中,气泡悬浮框常常用于页面中为某些对象显示提示信息,恰当地使用气泡悬浮框能够使网页布局更加完美 ...
- HTML5 respond.js 解决IE6~8的响应式布局问题
HTML5 respond.js 解决IE6~8的响应式布局问题 响 应式布局,理想状态是,对PC/移动各种终端进行响应.媒体查询的支持程度是IE9+以及其他现代的浏览器,但是IE8在市场当中仍然 ...