高分求助,多线程文件访问的问题!在线等待最优解决方案!

cnpeople 2002-12-17 04:58:08
多个线程(比如20个)如何对一个文件进行并发的读操作?
...全文
247 36 打赏 收藏 转发到动态 举报
写回复
用AI写文章
36 条回复
切换为时间正序
请发表友善的回复…
发表回复
HeShe 2003-02-26
  • 打赏
  • 举报
回复
先收藏,待会慢慢看
demetry 2003-02-26
  • 打赏
  • 举报
回复
我要看删去的代码^_^
JoeRen 2003-02-25
  • 打赏
  • 举报
回复
补充:关于模块封装性
ydaye先生跟icelight先生的争论,双方都有一些道理。但都可以完满地解决。
我以我上面介绍的QueueUserAPC为例说明,使用GetMessage时大致上可以用类似的方法解决

首先,QueueUserAPC函数使用PAPCFUNC回调,这个回调只有一个参数,既可以是整数也可以是指针,因此,它可以是指向对象的指针。谁创建,谁销毁这个原则是很重要的,但其中的主体是什么呢?当然不必是函数,它可以是一个对象。这样,如果使用能自我创建的对象(例如实现了Create方法的对象)获得指针,则销毁的义务是落在该对象本身上的。因此,对象的创建和销毁根本不必在同一线程,对象可以获得一定程度的自决权(可以决定什么时候销毁自己)。

例如

class APCClassTest
{
private:
APCClassTest(); //略去具体定义
public:
static APCClassTest *Create()
{
return new APCClassTest();
}
void Execute() //为了方便写在这里,最好把具体定义写在别处
{
//执行一些代码
...
//销毁自己
delete this;
}
static void CALLBACK AsyncExecute(ULONG_PTR param)
{
APCClassTest *pThis=(APCClassTest *)param;
pThis->Execute();
}
};

void main(int,char[])
{
... //其他代码
QueueUserAPC(APCClassTest::AsyncExecute,
threadhandle,(ULONG_PTR)APCClassTest::Create());
... //其他代码
}

谁敢说这样的代码封装性不好?
JoeRen 2003-02-25
  • 打赏
  • 举报
回复
除了楼上介绍的PostThreadMessage/GetMessage/PeekMessage(我以前常用),我向大家推荐一套新的API:QueueUserAPC/SleepEx/WaitForSingleObjectEx/WaitForMultipleObjectsEx

向指定线程的APC队列中添加一个异步过程调用(此后,只要被指定的线程进入警觉模式,系统就会调用指定的线程)
DWORD QueueUserAPC(PAPCFUNC pfnAPC,HANDLE thread,ULONG_PTR param)
其中PAPCFUNC的定义很简单
VOID (CALLBACK *PAPCFUNC)(ULONG_PTR dwParam);

等待指定的时间,并可以将线程置入警觉模式(这样才能执行异步过程调用)
DWORD SleepEx(DWORD dwMilliseconds,BOOL bAlertable)
当bAlertable参数为TRUE时,线程进入警觉模式。如果等待期间执行了异步过程调用,函数将提前返回(但有可能执行了几个异步过程调用之后才返回),返回值为WAIT_IO_COMPLETION

其他两个函数类似于SleepEx,但是可以同时等待一些同步对象进入传信状态,详见MSDN

注意事项:
1、使用QueueUserAPC向其它线程投递APC时,所用的线程句柄不能使用GetCurrentThread()的返回值,因为该值是个伪句柄,它总是表示当前线程。正确的方法是使用DuplicateHandle从当前线程中复制一个(必须指定THREAD_SET_CONTEXT访问标志)。当然,更方便的方法是在调用CreateThread时保存所返回的句柄(但这种方法不适用于主线程)。如果在Windows NT/2000/XP下,还可以使用较为简单的OpenThread调用来从线程ID获得句柄。
2、使用QueueUserAPC时,还需要将_WIN32_WINNT和_WIN32_WINDOWS这两个符号之一定义为大于0x400的值,因为该函数不能用于Windows95(_WIN32_WINDOWS=0x400),但可用于Windows95 OSR2(_WIN32_WINDOWS=0x401),也不能用于WindowsNT3.x

优点:
1、直接将处理函数排队,免去了分析消息类型的繁琐过程
2、可以结合重叠IO使用,提高单一线程的多路处理能力。
3、避开操作系统的消息回路,减轻系统负担。

缺点:
1、兼容性稍逊
2、(我正在想)
howtotell 2003-02-24
  • 打赏
  • 举报
回复
shareDenyAny吧。访问完毕以后就关闭。
riverking 2003-02-24
  • 打赏
  • 举报
回复
支持 ydaye(不用记,不用记,根本不用记!)

我是做通讯的,也常这样用,没有什么不好的。
demetry 2003-02-23
  • 打赏
  • 举报
回复
比较支持 icelight(icelight) ^_^
wx_xuan 2003-02-22
  • 打赏
  • 举报
回复
mark
ydaye 2003-02-22
  • 打赏
  • 举报
回复
这样是会令模块封装性遭到破坏,但是只要约定被遵守就是可以的,您看windows系统中一些网络函数就是由系统申请,用户释放。我是搞协议栈的,上层申请底层释放是提高效率的有效途径效率。
icelight 2003-02-22
  • 打赏
  • 举报
回复
不知道你公司情况如何,反正在我公司,“发送者申请,接收者释放”这种破坏模块封装性的做法是被禁止,除非你有非常非常非常充足的理由。

我公司认为没什么事情比模块的封装性更重要的事情了。

所以,我认可用互斥量的方案。简单,易懂,性能够好。
最重要的是,封装性比你的方法要完美多了。

唔,既然我俩讨论得差不多,也该停了。好让其他真正高手说句话,就算说我是井中蛙也无妨,只要能看到精辟的见解。哧哧。
ydaye 2003-02-22
  • 打赏
  • 举报
回复
改了几句:
int *p = new int[0x10000];
p[0xffff] = 7;

((int*)(msg.wParam))[0xffff] = 6;

之后在GetMessage前加了一句Sleep(10000),结果依旧正确。

“能在指针变无效前就取得值了。当系统繁忙时,你不会有这种运气。”
指针为什么要改变?若发送线程以为发了消息就可以释放内存或者把内存挪作他用就是程序本身的问题了,而不是系统问题了,我想只要发送者和接收者形成“发送者申请,接收者释放”的协定,就不会出现上述问题,就算windows再烂,我想系统这样的不稳定性是不会存在的,否则何以运行至今?

是的,只有发生保护模式异常,如:非法申请的内存(就是只有虚地址,没映射到物理空间的内存)和企图写不可写的内存区域才会出现那个对话框。类似于越界访问的问题自然是不会出现了,但是这类问题难道也要交给系统解决么?那不如用java了,c/c++只怕是搞不定。

不好意思,再次用了“只怕是”。在这个周遭环境就很不严谨的地方我又何需再四斟酌?
icelight 2003-02-22
  • 打赏
  • 举报
回复
GetMessage()前加一句::sleep(1000)。

注意,有些非法访问是不会出现对话框的,你只能在Debug版的output窗口看到,请留意VC的输出窗口。
icelight 2003-02-22
  • 打赏
  • 举报
回复
楼上,请在GetMessage()加一句::sleep(1000)。
还有请将整形指针改为字符串指针,字符串长度有一两K就够了。

这不是极端做法,而是模拟系统繁忙时的真实表现,你能理解。

之所以提出疑问,是因为在系统空闲时,GetMessage()的处理速度很快,能在指针变无效前就取得值了。当系统繁忙时,你不会有这种运气。

请试试。还有,作技术讨论时请勿使用"估计","只怕是不会"这类词,我们需要的是动手调试,你认为呢。
ydaye 2003-02-22
  • 打赏
  • 举报
回复
您能不能抽出时间在思考的同时试一试呢?请您放心,在谈出想法之前,我已试过了。

既然您对此表示怀疑,那么请看程序片断,希望能对您有所帮助:

发送:
int *p = new int;
*p = 7;

CloseHandle(CreateThread(NULL,
0,
TProc,
NULL,
0,
&id));

PostThreadMessage(id, WM_USER, (WPARAM)p, 0);

接收:
GetMessage(&msg, NULL, 0, 0);
*((int*)(msg.wParam)) = 6;

运行结果首先是没报错,其次是*((int*)(msg.wParam))改之前是7,改之后是6,不知道是不是可以说明一些问题呢?

再说说PostMessage和PostThreadMessage,一样吗?能一样吗?一样要两个函数干嘛?看看msdn,PostMessage的第一个参数,当为NULL时,则“The function behaves like a call to PostThreadMessage with the dwThreadId parameter set to the identifier of the current thread.”,like,仅仅是like而已,况且是发给本线程。

最后说说指针,同一进程的不同线程,共享一个地址空间,线程a的指针对线程b同样有效,只怕是不会非法访问的吧。楼上所担心的是不同进程的线程之间通信吧,那当然不能传指针了。这个问题所涉及到的两个线程没有必要在两个进程之中吧。

本来还想就csdn里面的人的水平(提问和回答)和一些风气发发牢骚(并非针对楼上,不要误会),想想还是算了,我算老几啊?就这么着吧。
icelight 2003-02-22
  • 打赏
  • 举报
回复
建议楼上动手写段代码试验一下。只是用嘴巴说未免无趣。

PostThreadMessage等于PostThread(NULL,,,,,),调用后立即返回。
若参数含指针的话,目标线程一用就出非法读取错误。这与WINDOW知不知道它是指针无关,只与你的目标线程是否把它当作指针有关。
不知我说清楚没有,不知你看明白没有。

平时回贴习惯了用冷冰冰的口吻,虽然并没激动情绪,但难免会让人误会,只能说声,抱歉。
纯属技术讨论,有无对错欢迎指出,我不是说我一定正确,我只说我在思考。
ydaye 2003-02-21
  • 打赏
  • 举报
回复
忘了说了,如上所述,当然不会发生阻赛问题,与互斥量阻塞 '写线程' 还是有明显区别的。
ydaye 2003-02-21
  • 打赏
  • 举报
回复
不要激动嘛,

below WM_USER的消息在异步消息处理中才不能发指针。

此例中的消息在WM_USER前面显然是没什么意义的吧,退一步说,不谈范围,对于自定义的消息,带个参数,在windows看来都是整数,他windows怎么就能就知道那是个指针呢?呵呵。

最后,此例中明显不必要用SendMessage和PostMessage,何需创建窗体呢?用PostThreadMessage不就结了?
icelight 2003-02-21
  • 打赏
  • 举报
回复
message的两个参数还要带上要写到文件里的字符串吧?
有指针的话就要用到SendMessage()而不是PostMessage()了吧?
用了SendMessage就阻塞了,与互斥量阻塞 '写线程' 有何区别?
绕了个大弯,吃力不讨好。
fengge8ylf 2003-02-21
  • 打赏
  • 举报
回复
以前我处理过这个问题,用的是内存映射文件
ydaye 2003-02-21
  • 打赏
  • 举报
回复
不需要在考虑同步了,这里的queue可以就是那个写线程的message queue,搞完一个再搞下一个不就行了,其他线程需要写就发消息,而真正写时一定是一个一个写的,隐含地就把同步问题解决了。
加载更多回复(16)

15,471

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC 进程/线程/DLL
社区管理员
  • 进程/线程/DLL社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧