轉載:https://blog.csdn.net/u012218838/article/details/79362929(sqlite3 使用讀寫鎖SRWLOCK例子)
轉載:https://my.oschina.net/u/1426828/blog/1793762(SRWLock介紹使用)
轉載:https://www.cnblogs.com/5iedu/p/4727734.html(SRWLock使用demo)
轉載:https://docs.microsoft.com/zh-cn/windows/desktop/api/synchapi/nf-synchapi-acquiresrwlockexclusive(SRWLock官方文檔)
轉載:https://blog.csdn.net/MoreWindows/article/details/7650574
讀寫鎖在對資源進行保護的同時,還能區分想要讀取資源值的線程(讀取者線程)和想要更新資源的線程(寫入者線程)。對於讀取者線程,讀寫鎖會允許他們並發的執行。當有寫入者線程在占有資源時,讀寫鎖會讓其它寫入者線程和讀取者線程等待。因此用讀寫鎖來解決讀者寫者問題會使代碼非常清晰和簡潔。
SRWLock
從visual studio2005開始你可以使用SRWLock,和CRITICAL_SECTION(關鍵段)差不過的功能,不同的是由程序員控制讀寫線程,如果是讀線程,可以同時讀取,如果是寫線程,則其他線程掛起,寫完后馬上就可以讀取
首先,需要一個SRWLOCK結構,然后調用InitializeSRWLock(PSWRLOCK srwLock)函數初始化這個結構。
SRWLOCK srw; InitializeSRWLock(&srw); 一旦初始化完成,就可以對寫入線程調用AcquireSRWLockExclusive()函數和ReleaseSRWLockExclusive()函數 AcquireSRWLockExclusive(&srw); //...寫入數據,寫入東西的時候該線程獨占,其他任何線程不可進入 ReleaseSRWLockExclusive(&srw); 對於只讀線程可以調用AcquireSRWLockShared()函數和ReleaseSRWLockShared()函數,如下 AcquireSRWLockShared(&srw); //..讀取數據,如果這時沒有寫入數據則多個讀取線程可以進行 ReleaseSRWLockShared)&srw);
讀/寫鎖
SRWLock的目的和關鍵段相同:對一個資源進行保護,不讓其它線程訪問它。但是,與關鍵段不同的是,SRWLock允許我們區分哪些想要讀取資源的值的線程(讀取者線程)和想要更新資源的值的線程(寫入者線程)。讓所有的讀取者線程在同一時刻訪問共享資源應該是可行的,這是因為僅僅讀取資源的值並不存在破壞數據的風險。只有當寫入者線程想要對資源進行更新的時候才需要進行同步。在這種情況下,寫入者線程想要對資源進行更新的時候才需要進行同步。在這種情況下,寫入者線程應該獨占對資源的訪問權:任何其它線程,無論是讀取者線程還是寫入者線程,都不允許訪問資源。這就是SRWLock提供的全部功能。
首先,我們需要分配一個SRWLOCK結構並用InitializeSRWLock函數對它進行初始化:
void InitializeSRWLock(PSRWLOCK SRWLock);
一旦SRWLock初始化完成之后,寫入者線程就可以調用AcquireSRWLockExclusive,將SRWLOCK對象的地址作為參數傳入,以嘗試獲得對被保護資源的獨占訪問權。
void AcquireSRWLockExclusive(PSRWLOCK SRWLock);
完成對資源的更新之后,應該調用ReleaseSRWLockExclusice,並將SRWLOCK對象的地址作為參數傳入,這樣就解除了對資源的鎖定。
void ReleaseSRWLockExclusive(PSRWLOCK SRWLock);
對讀取者線程來說,同樣有兩個步驟,單調用的是下面兩個新的函數:
void AcquireSRWLockShared(PSRWLOCK SRWLock); void ReleaseSRWLockShared(PSRWLOCK SRWLock);
SRWLock鎖的共享規則:
①若當前鎖的狀態是“寫”(即某個線程已經獲得排它鎖),這時其他線程,不管是申請讀或寫鎖的線程,都會被阻塞在AcquireSRWLock*函數中。讀鎖或寫鎖等待計數加1。
②若當前鎖的狀態是“讀”(即某個(些)線程已經獲得了共享鎖)。
A、如果新的線程申請寫鎖,則此時它將被掛起,鎖的寫等待計數加1。直至當前正在讀鎖的線程全部結束,然后系統會喚醒正在等待寫的線程,即申請排他鎖要在沒有任何其他鎖的時候才能返回。
B、如果新的線程申請讀鎖,若此時沒有寫線程正在等待,則允許讀鎖進入而不會被阻塞。如果有寫鎖正在等待,則寫鎖優先得到鎖,新線程進入等待,讀鎖計數加1(這樣做的目的是讓寫鎖有機會進入)。
不存在用來刪除或銷毀SRWLOCK的函數,系統會自動執行清理工作。
與關鍵段相比,SRWLock缺乏下面兩個特性:
1)不存在TryEnter(Shared/Exclusive)SRWLock 之類的函數:如果鎖已經被占用,那么調用AcquireSRWLock(Shared/Exclusive) 會阻塞調用線程。
2)不能遞歸地調用SRWLOCK。也就是說,一個線程不能為了多次寫入資源而多次鎖定資源,然后再多次調用ReleaseSRWLock* 來釋放對資源的鎖定。
總結,如果希望在應用程序中得到最佳性能,那么首先應該嘗試不要共享數據,然后依次使用volatile讀取,volatile寫入,Interlocked API,SRWLock以及關鍵段。當且僅當所有這些都不能滿足要求的時候,再使用內核對象。因為每次等待和釋放內核對象都需要在用戶模式和內核模式之間切換,這種切換的CPU開銷非常大。
具體參考代碼:
//讀者與寫者問題繼 讀寫鎖SRWLock #include <stdio.h> #include <process.h> #include <windows.h> //設置控制台輸出顏色 BOOL SetConsoleColor(WORD wAttributes) { HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); if (hConsole == INVALID_HANDLE_VALUE) return FALSE; return SetConsoleTextAttribute(hConsole, wAttributes); } const int READER_NUM = 5; //讀者個數 //關鍵段和事件 CRITICAL_SECTION g_cs; SRWLOCK g_srwLock; //讀者線程輸出函數(變參函數的實現) void ReaderPrintf(char *pszFormat, ...) { va_list pArgList; va_start(pArgList, pszFormat); EnterCriticalSection(&g_cs); vfprintf(stdout, pszFormat, pArgList); LeaveCriticalSection(&g_cs); va_end(pArgList); } //讀者線程函數 unsigned int __stdcall ReaderThreadFun(PVOID pM) { ReaderPrintf(" 編號為%d的讀者進入等待中...n", GetCurrentThreadId()); //讀者申請讀取文件 AcquireSRWLockShared(&g_srwLock); //讀取文件 ReaderPrintf("編號為%d的讀者開始讀取文件...n", GetCurrentThreadId()); Sleep(rand() % 100); ReaderPrintf(" 編號為%d的讀者結束讀取文件n", GetCurrentThreadId()); //讀者結束讀取文件 ReleaseSRWLockShared(&g_srwLock); return 0; } //寫者線程輸出函數 void WriterPrintf(char *pszStr) { EnterCriticalSection(&g_cs); SetConsoleColor(FOREGROUND_GREEN); printf(" %sn", pszStr); SetConsoleColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); LeaveCriticalSection(&g_cs); } //寫者線程函數 unsigned int __stdcall WriterThreadFun(PVOID pM) { WriterPrintf("寫者線程進入等待中..."); //寫者申請寫文件 AcquireSRWLockExclusive(&g_srwLock); //寫文件 WriterPrintf(" 寫者開始寫文件....."); Sleep(rand() % 100); WriterPrintf(" 寫者結束寫文件"); //標記寫者結束寫文件 ReleaseSRWLockExclusive(&g_srwLock); return 0; } int main() { printf(" 讀者寫者問題繼 讀寫鎖SRWLockn"); printf(" ----nn"); //初始化讀寫鎖和關鍵段 InitializeCriticalSection(&g_cs); InitializeSRWLock(&g_srwLock); HANDLE hThread[READER_NUM + 1]; int i; //先啟動二個讀者線程 for (i = 1; i <= 2; i++) hThread[i] = (HANDLE)_beginthreadex(NULL, 0, ReaderThreadFun, NULL, 0, NULL); //啟動寫者線程 hThread[0] = (HANDLE)_beginthreadex(NULL, 0, WriterThreadFun, NULL, 0, NULL); Sleep(50); //最后啟動其它讀者結程 for ( ; i <= READER_NUM; i++) hThread[i] = (HANDLE)_beginthreadex(NULL, 0, ReaderThreadFun, NULL, 0, NULL); WaitForMultipleObjects(READER_NUM + 1, hThread, TRUE, INFINITE); for (i = 0; i < READER_NUM + 1; i++) CloseHandle(hThread[i]); //銷毀關鍵段 DeleteCriticalSection(&g_cs); return 0; }
sqlite 寫線程
unsigned WINAPI WriteThreads(void *pPM) { while(1) { Sleep(2000); AcquireSRWLockExclusive(&srwLock); printf("寫線程\n"); char chsql[1024] = {0}; sprintf(chsql,"insert into tab1 values ('thread 0X%08X write 1')",GetCurrentThreadId()); int nret = sqlite3_exec(m_db,chsql,NULL,NULL,NULL); if(nret != SQLITE_OK) { printf("================write error============\n"); } ReleaseSRWLockExclusive(&srwLock); } return 0; }
讀線程
unsigned WINAPI ReadThreads(void *pPM) { while(1) { Sleep(2000); AcquireSRWLockShared(&srwLock); printf("讀線程\n"); char chsql[1024] = {0}; sprintf(chsql,"select id from tab1 "); int nret = sqlite3_exec(m_db,chsql,NULL,NULL,NULL); if(nret != SQLITE_OK) { printf("================read error============\n"); } ReleaseSRWLockShared(&srwLock); } return 0; }