访问具有多个线程的单个文件

Ray*_*son 17 windows delphi file-io multithreading

我需要与多个线程同时访问一个文件.这需要同时完成,不出于性能原因而没有线程序列化.

特别是文件是使用'临时'文件属性创建的,该属性鼓励窗口将文件保存在系统缓存中.这意味着文件读取的大部分时间不会靠近磁盘,但会从系统缓存中读取文件的一部分.

能够同时访问此文件将显着提高我的代码中某些算法的性能.

所以,这里有两个问题:

  1. Windows是否可以从不同的线程同时访问同一个文件?
  2. 如果是这样,你如何提供这种能力?我已经尝试创建临时文件并再次打开文件以提供两个文件句柄,但第二个打开不成功.

这是创造:

FFileSystem := CreateFile(PChar(FFileName),
                          GENERIC_READ + GENERIC_WRITE,
                          FILE_SHARE_READ + FILE_SHARE_WRITE,
                          nil,
                          CREATE_ALWAYS,
                          FILE_ATTRIBUTE_NORMAL OR
                          FILE_FLAG_RANDOM_ACCESS OR
                          FILE_ATTRIBUTE_TEMPORARY OR
                          FILE_FLAG_DELETE_ON_CLOSE,
                          0);
Run Code Online (Sandbox Code Playgroud)

这是第二次开放:

FFileSystem2 := CreateFile(PChar(FFileName),
                          GENERIC_READ,
                          FILE_SHARE_READ,
                          nil,
                          OPEN_EXISTING,
                          FILE_ATTRIBUTE_NORMAL OR
                          FILE_FLAG_RANDOM_ACCESS OR
                          FILE_ATTRIBUTE_TEMPORARY OR
                          FILE_FLAG_DELETE_ON_CLOSE,
                          0);
Run Code Online (Sandbox Code Playgroud)

到目前为止,我尝试过各种标志组合,但没有成功.打开的第二个文件总是失败,并且消息表明该文件在被另一个进程使用时无法访问.

编辑:

好的,还有更多的信息(我希望不要迷失在这里的杂草......)

有问题的过程是在WinXP 64上运行的Win32服务器进程.它维护大型空间数据库,并希望在L1/L2缓存结构的内存中保留尽可能多的空间数据库.L1已经存在.L2作为一个"临时"文件存在,它保留在Windows系统缓存中(这有点像一个肮脏的技巧,但在某种程度上绕过win32内存限制).Win64意味着我可以拥有系统缓存使用的大量内存,因此用于保存L2缓存的内存确实会计入进程内存.

多个(可能很多)线程希望同时访问L2缓存中包含的信息.目前,访问是序列化的,这意味着一个线程可以读取它的数据,而大多数(或其余的)线程被阻塞,等待该操作完成.

L2高速缓存文件确实被写入,但我很乐意全局序列化/交错读写类型操作,只要我可以执行并发读取.

我知道存在令人讨厌的潜在线程并发问题,而且我知道有很多方法可以在其他环境中对这只猫进行修饰.我有这个特定的上下文,我正在尝试确定是否有一种方法允许在文件内和同一进程内进行并发线程读取访问.

我考虑的另一种方法是将L2缓存拆分为多个临时文件,其中每个文件以当前单个L2缓存文件的方式串行化线程访问.

是的,这种有点绝望的方法是因为64位Delphi不会很快与我们在一起:-(

谢谢,雷蒙德.

Rob*_*edy 18

是的,程序可以从不同的线程多次打开同一个文件.但是,您希望在写入文件的同时避免从文件中读取文件.您可以使用它TMultiReadExclusiveWriteSynchronizer来控制对整个文件的访问.它比一个关键部分更少序列化.要进行更精细的控制,请查看根据LockFileEx需要控制对文件特定区域的访问.写作时,请求独占锁定; 阅读时,共享锁.

对于您发布的代码,File_Share_Write在初始共享标志中指定意味着所有后续打开操作也必须共享文件以进行写入.引用文档:

如果未指定此标志,但文件或设备已打开以进行写访问或具有写访问的文件映射,则该函数将失败.

你的第二个打开请求是说它不希望任何其他人在该句柄保持打开时被允许写入该文件.因为已经有其他处理打开那确实让写作,第二请求不能得到满足.GetLastError应该已经返回32,这Error_Sharing_Violation正是文档所说的应该发生的事情.

指定File_Flag_Delete_On_Close意味着所有后续打开请求都需要共享文件以进行删除.文档再次:

除非FILE_SHARE_DELETE指定了共享模式,否则对文件的后续打开请求将失败.

然后,由于第二个打开请求共享文件以进行删除,因此所有其他打开句柄也必须共享它以进行删除.文件:

如果文件存在打开的句柄,则调用将失败,除非它们都是以FILE_SHARE_DELETE共享模式打开的.

最重要的是,要么每个人都共享,要么根本没有共享.

FFileSystem := CreateFile(PChar(FFileName),
  Generic_Read or Generic_Write
  File_Share_Read or File_Share_Write or File_Share_Delete,
  nil,
  Create_Always,
  File_Attribute_Normal or File_Flag_Random_Access
    or File_Attribute_Temporary or File_Flag_Delete_On_Close,
  0);

FFileSystem2 := CreateFile(PChar(FFileName),
  Generic_Read,
  File_Share_Read or File_Share_Write or File_Share_Delete,
  nil,
  Open_Existing,
  File_Attribute_Normal or File_Flag_Random_Access
    or File_Attribute_Temporary or File_Flag_Delete_On_Close,
  0);
Run Code Online (Sandbox Code Playgroud)

换句话说,除了第五个参数之外,所有参数都是相同的.

这些规则适用于在同一线程上打开的两次尝试以及来自不同线程的尝试.


mek*_*ian 6

更新#2

我在C中写了一些测试项目来尝试解决这个问题 - 尽管Rob Kennedy在我不在的时候打败了我.他概述了这两种情况,包括交叉过程.这是一个链接,如果其他人想看到这个在行动.

SharedFileTests.zip(VS2005 C++解决方案)@ meklarian.com

有三个项目:

InProcessThreadShareTest - 测试创建者和客户端线程.
InProcessThreadShareTest.cpp Snippet @ gist.github

SharedFileHost - 创建运行1分钟并更新文件的主机.
SharedFileClient - 创建运行30秒的客户端并轮询文件.
SharedFileHost.cpp和SharedFileClient.cpp Snippet @ gist.github

所有这些项目都假定位置C:\ data\tmp\sharetest.txt是可创建和可写的.


更新

鉴于你的情况,听起来你需要一大块内存.您可以使用AWE访问超过4Gb的内存,而不是游戏系统缓存,但您需要一次映射部分内容.这应该涵盖您的L2场景,因为您希望确保使用物理内存.

地址窗口扩展@ MSDN

使用AllocateUserPhysicalPages和VirtualAlloc来保留内存.

AllocateUserPhysicalPages函数(Windows)@ MSDN
VirtualAlloc函数(Windows)@ MSDN


初始

鉴于您正在使用标志FILE_FLAG_DELETE_ON_CLOSE,您是否有任何理由不考虑使用内存映射文件?

在Win32 @ MSDN中管理内存映射文件

从我在CreateFile语句中看到的内容,您似乎希望跨线程或跨进程共享数据,而不仅仅是在任何会话打开时存在相同的文件.内存映射文件允许您在所有会话中使用相同的逻辑文件名.另一个好处是,您可以在所有会话中安全地映射视图并锁定映射文件的某些部分.如果您有一个具有N客户端方案的严格服务器,则应该易于实现.如果您遇到任何客户端可能是开放服务器的情况,您可能希望考虑使用其他一些机制来确保只有一个客户端首先启动服务文件(可能通过全局互斥锁).

CreateMutex @ MSDN

如果您只需要单向传输数据,也许您可​​以使用命名管道.
(编辑)这最适合1台服务器到1台客户端.

命名管道(Windows)@ MSDN