转载: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#include #include //设置控制台输出颜色 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; }