第一次使用博客,把自己学习的心得记录下来,与大家分享,有什么不足请指正,共同学习!
本文记录的是线程同步的一个经典问题,读写问题。这个场景在实际的应用中很常见,多线程中同时对文件进行读写很容易出问题,在此啰嗦一下,主要问题无外乎以下几种:
1.**丢失修改**。当A对文件进行修改后还没来得及更新,这时候B又对文件进行操作并将A的修改覆盖了,导致A实际保存的结果是B操作的结果;
2.**不可重复**: A对数据进行读取,在修改的过程中,B又对数据进行了读取并在A修改前进行了保存,当A再次读取文件进行结果验证的时候,发现啥都没做,结果就不对了。
其实就是对文件的操作没有保持原子性,这就需要通过互斥来保证同一时间只有一个线程能对文件进行操作;同时是谁先操作,谁继续操作(比如财务报销签字,先找部门领导,再找财务,顺序不对肯定签不了字,理论情况下),这就需要通过同步机制来保证。
说了这么多概念,回到读写者问题。读者写者问题描述非常简单,有一个写者很多读者,多个读者可以同时读文件,但写者在写文件时不允许有读者在读文件,同样有读者在读文件时写者也不去能写文件。
![在这里插入图片描述](https://img-blog.csdnimg.cn/2e9e29b0bded455a8ccf7719a563b388.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAdzE1MTMxMjc3MTgw,size_12,color_FFFFFF,t_70,g_se,x_16#pic_center)
思路: 首先分析以上情况,基于以上的介绍,只要多线程操作文件的,肯定需要保持原子性,即互斥;其次肯定是有先后顺序的,即先向缓存内写入实际数据,当缓存区有了数据后才可以读取,即同步。
1.对于互斥,在Windows编程中,有很多种方法:互斥锁(Mutex),关键段(Critical Section等,我在这里用的是互斥锁Mutex;
2.对于同步。先分析问题种需要怎样的顺序。很简单,首先必须先写入缓存,当写入完毕的时候,通知读取的线程缓存区有内容了,缓存线程进行读取;缓存线程都读取完毕后,由**最后一个**读取的线程通知写入线程可以继续写入。
以上是问题的分析。
下面是我的实现过程,直接上代码,更多的是记录如何使用这些API:
#include "stdafx.h"
#include <Windows.h>
#include <iostream>
//-------------------------------------------读写同步问题----------------------------
/*
一个用户写入和多个用户读取问题
读取的时候不所有用户不可以写入
写入的时候所有用户不可以读取
思路: 写入的时候先等待写入信号量(初始化时可以先写入的,即信号量有写入资源数);
设置一个已读取用户的个数变量,每一个读取的用户读取前先和总户数作比较,确认自己是否是最后一个读取用户
最后一个结束读文件的读者线程要负责通知读取线程可以继续写入。
总结 : 1.写入的时候先等待写入信号,写入完释放读取信号
2.读取的时候先读取写入信号,读取完再释放写入信号
3.同时,对数据操作的时候添加互斥锁
*/
extern HANDLE g_Mutex; //缓存区访问互斥锁
HANDLE g_ReadSemaphore, g_WriteSemaphore; //读写信号量
HANDLE thread[4]; //读写线程句柄
const unsigned int g_Size=3; //总用户数
const unsigned int g_BufferSize=8; //总的写入次数
int buffer; //读写的缓存区
int g_Current_Read; //当前读取用户个数
unsigned int __stdcall writeThread(PVOID pM){
for (int i = 0; i < g_BufferSize; i++){
WaitForSingleObject(g_WriteSemaphore, INFINITE);
WaitForSingleObject(g_Mutex, INFINITE);
buffer = i;
printf("Writer is writting.. write buffer is:%d\n", buffer);
ReleaseMutex(g_Mutex);
ReleaseSemaphore(g_ReadSemaphore, g_Size, NULL);
}
return 0;
}
unsigned int __stdcall readThread(PVOID pM){
while (true){
WaitForSingleObject(g_ReadSemaphore, INFINITE);
WaitForSingleObject(g_Mutex, INFINITE);
printf("Reader %d is reading.. read buffer is:%d\n", g_Current_Read,buffer);
g_Current_Read++;
ReleaseMutex(g_Mutex);
if (g_Current_Read == g_Size ){
ReleaseSemaphore(g_WriteSemaphore, 1, NULL);
g_Current_Read = 0;
//这里要注意:这里判断的是自己(读取线程)是不是最后一个读取的线程
//如果是,那么就要向写入线程发送信号,告诉它可以继续写入
//同时将g_Current_Read清零,即当前位置计数。
}
}
return 0;
}
void useReadW(){
g_ReadSemaphore = CreateSemaphore(NULL, 0, 3, NULL);
//注意,CreateSemaphore的第2个参数是初始资源值,因为初始不能触发,必须保证写入线程发信号才能执行,所以这里置0
//第3个参数是3,表示最大资源数为3,写入线程写入完毕后将资源加3,这三个读取线程可以读取缓存数据,因为已经实现了互斥,所以不会出问题
//第4个参数是name,可以指定名称,当在多个进程种要实现线程的同步时,可以通过OpenSemaphore来打开此名字对应的信号锁。
g_WriteSemaphore = CreateSemaphore(NULL, 1, 1, NULL);
g_Mutex = CreateMutex(NULL, FALSE, NULL);
//以上分别创建信号量、互斥锁
//写入信号量g_WriteSemaphore初始资源为1(第2个参数),表示初始可以先写入,
//其最大资源数也是1.这就侧面说明信号量也可以实现互斥,即信号量是一种特殊的互斥
//这里的同步其实也可以用事件来实现,但是多个线程通知我觉得还是信号量方便一些
thread[0] = (HANDLE)_beginthreadex(NULL, 0, writeThread, NULL, 0, NULL);
thread[1] = (HANDLE)_beginthreadex(NULL, 0, readThread, NULL, 0, NULL);
thread[2] = (HANDLE)_beginthreadex(NULL, 0, readThread, NULL, 0, NULL);
thread[3] = (HANDLE)_beginthreadex(NULL, 0, readThread, NULL, 0, NULL);
//初始同时启动读写线程,启动顺序没有要求,因为已经实现的同步
//我查了以下,_beginthreadex内部其实调用的是CreateThread函数接口,
//在此做了封转,保证了线程的安全性,建议用此方法。
WaitForMultipleObjects(4, thread, TRUE, INFINITE);
//此函数功能是让主线程阻塞,直到所有线程执行完毕后再继续
//第1个参数是等待的线程数目
//第2个参数的线程数组
//第3个参数为true表示需要所有的线程都发出信号后才结束阻塞
//第4个参数表示一直等待下去,不超时
int i = 0;
while (i < 4){
CloseHandle(thread[i]); //关闭读写线程句柄
}
CloseHandle(g_ReadSemaphore);
CloseHandle(g_WriteSemaphore); //关闭读写信号量句柄
CloseHandle(g_Mutex); //关闭互斥锁句柄
}
以下是运行的结果:
好了,今天就先记录到这里,有问题多谢指点!