8.5 Slim读/写锁(SRWLock)——轻量级的读写锁windows
(1)SRWLock锁的目的数组
①容许读者线程同一时刻访问共享资源(由于不存在破坏数据的风险)函数
②写者线程应独占资源的访问权,任何其余线程(含写入的线程)要等这个写者线程访问完才能得到资源。性能
(2)SRWlock锁的使用方法测试
①初始化SRWLOCK结构体 InitializeSRWLock(PSRWLOCK pSRWLock);ui
②写者线程调用AcquireSRWLockExclusive(pSRWLock);以排它方式访问spa
读者线程调用AcquireSRWLockShared以共享方式访问线程
③访问完毕后,写者线程调用ReleaseSRWLockExclusive解锁。读者线程要调用ReleaseSRWLockShared解锁指针
④注意SRWLock不须要删除和销毁,因此不用Delete之类的,系统会自动清理。code
(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 = 20; int g_iGlobalValue = 0; ////////////////////////////////////////////////////////////////////////// SRWLOCK g_sl = { 0 }; ////////////////////////////////////////////////////////////////////////// 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 = 0; SYSTEM_INFO si = { 0 }; GetSystemInfo(&si); for (int i = 0; i < g_iThreadCnt;i++){ if (0 == rand()%2) aThread[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)WriteThread, NULL, CREATE_SUSPENDED,&dwThreadId); else aThread[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ReadThread, NULL, CREATE_SUSPENDED, &dwThreadId); SetThreadAffinityMask(aThread[i],1<<(i % si.dwNumberOfProcessors)); ResumeThread(aThread[i]); } //等待全部线程结束 WaitForMultipleObjects(g_iThreadCnt,aThread,TRUE,INFINITE); for (int i = 0; i < g_iThreadCnt;i++){ CloseHandle(aThread[i]); } _tsystem(_T("PAUSE")); return 0; } //////////////////////////////////////////////////////////////////////////// ////不加保护的读写 //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 0; } ////////////////////////////////////////////////////////////////////////// DWORD WINAPI WriteThread(PVOID pParam) { //写时以排它的方式 __try{ AcquireSRWLockExclusive(&g_sl); for (int i = 0; i <= 4321; 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 0; }
(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) * 1000) / m_liPerfFreq.QuadPart); } private: LARGE_INTEGER m_liPerfFreq; // Counts per second LARGE_INTEGER m_liPerfStart; // Starting count }; ////////////////////////////////////////////////////////////////////////// DWORD g_nIterations = 1000000; //叠代次数 typedef void(CALLBACK* OPERATIONFUNC)(); DWORD WINAPI ThreadInterationFunction(PVOID operationFunc){ OPERATIONFUNC op = (OPERATIONFUNC)operationFunc; for (DWORD iteration = 0; iteration < g_nIterations;iteration++){ op(); } return 0; } ////////////////////////////////////////////////////////////////////////// //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 = 0; i < nThreads;i++){ phThreads[i] = CreateThread(NULL, 0, ThreadInterationFunction, //线程函数 opfOperationFunc,//线程函数的参数(是个函数指针) 0, //当即运行 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 = 0; i < nThreads; i++){ CloseHandle(phThreads[i]); } delete phThreads; } ////////////////////////////////////////////////////////////////////////// //测试列表: //一、不加同步锁,对整型volatile变量直接读取 //二、使用InterlockedIncrement对整型变量进行写操做 //三、使用临界区对整型volatile变量进行读取 //四、使用SRWLock锁对整型volatile变量进行读写操做 //五、使用互斥对象Mutex对整型volatile变量进行读取操做 volatile LONG gv_value = 0; //一、直接读写volatile型的整型变量 //'lValue':是个局部变量,己初始化,但未被引用,编译器 //会出现警告,可禁用该警告 #pragma warning(disable:4189) void WINAPI VolatileReadCallBack() { LONG lValue = gv_value; } #pragma warning(default:4189) void WINAPI VolatileWriteCallBack() { gv_value = 0; } //二、使用InterlockedIncrement对整型变量进行写操做 void WINAPI InterlockedIncrementCallBack() { InterlockedIncrement(&gv_value); } //三、使用临界区对整型volatile变量进行读取 CRITICAL_SECTION g_cs; void WINAPI CriticalSectionCallBack() { EnterCriticalSection(&g_cs); gv_value = 0; LeaveCriticalSection(&g_cs); } //四、使用SRWLock锁对整型volatile变量进行读写操做 SRWLOCK g_srwLock; void WINAPI SRWLockReadCallBack() { AcquireSRWLockShared(&g_srwLock); gv_value = 0; ReleaseSRWLockShared(&g_srwLock); } void WINAPI SRWLockWriteCallBack() { AcquireSRWLockExclusive(&g_srwLock); gv_value = 0; ReleaseSRWLockExclusive(&g_srwLock); } //五、使用互斥对象Mutex对整型volatile变量进行读取操做 HANDLE g_hMutex; void WINAPI MutexCallBack() { WaitForSingleObject(g_hMutex, INFINITE); gv_value = 0; ReleaseMutex(g_hMutex); } ////////////////////////////////////////////////////////////////////////// int _tmain() { _tsetlocale(LC_ALL, _T("chs")); //分别测试当线程总数为一、二、4时各类锁切换所花费的时间 for (int nThreads = 1; nThreads <= 4;nThreads *= 2){ //一、直接读写 MeasureConcurrentOperation(_T("Volatile Read"), nThreads, VolatileReadCallBack); MeasureConcurrentOperation(_T("Volatile Write"), nThreads, VolatileWriteCallBack); //二、InterlockedIncrement MeasureConcurrentOperation(_T("Interlocked Increment"), nThreads, InterlockedIncrementCallBack); //三、临界区: InitializeCriticalSection(&g_cs);//初始化临界区 MeasureConcurrentOperation(_T("Critical Section"), nThreads, CriticalSectionCallBack); DeleteCriticalSection(&g_cs); //四、SRWLock锁: InitializeSRWLock(&g_srwLock);//初始化SRWLock锁,注意不须要释放,系统会自行回收 MeasureConcurrentOperation(_T("SRWLock Read"), nThreads, SRWLockReadCallBack); MeasureConcurrentOperation(_T("SRWLock Write"), nThreads, SRWLockWriteCallBack); //五、互斥对象: g_hMutex = CreateMutex(NULL, false, NULL);//准备互斥对象 MeasureConcurrentOperation(_T("Mutex"), nThreads, MutexCallBack); CloseHandle(g_hMutex); _tprintf(_T("\n")); } return 0; }