使用多个线程访问单个文件

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

该文件特别是用“临时”文件属性创build的,它鼓励Windows将文件保存在系统caching中。 这意味着大部分时间文件读取不会靠近磁盘,而是从系统caching中读取文件的一部分。

能够同时访问这个文件将显着提高我的代码中某些algorithm的性能。

所以,这里有两个问题:

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

这是创build:

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); 

这是第二次打开:

 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); 

我已经试过各种组合的标志,迄今为止没有成功。 第二个文件打开总是失败,消息的影响是文件无法访问,因为它正在被另一个进程使用。

编辑:

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

有问题的进程是在WinXP 64上运行的Win32服务器进程。它维护着大型的空间数据库,并希望在L1 / L2caching结构的内存中尽可能多地保留空间数据库。 L1已经存在。 L2作为一个“临时”文件存在,保留在windows系统caching中(这是一个肮脏的伎俩,但绕过win32的内存限制)。 Win64意味着系统caching可以使用大量内存,因此用于保存L2caching的内存不会占用进程内存。

多个(可能很多)线程想并发地访问L2caching中包含的信息。 目前,访问是序列化的,这意味着一个线程读取它的数据,而大部分(或其他)的线程被阻塞,等待完成该操作。

二级caching文件没有被写入,但是我很高兴全局串行化/交错读取和写入types的操作,只要我可以执行并发读取。

我知道有令人讨厌的潜在的线程并发问题,我知道在其他上下文中有几十种方法来剥皮这个猫。 我有这个特定的上下文,我试图确定是否有一种方法来允许在同一个进程中的文件并发线程读取访问。

我考虑过的另一种方法是将二级caching分成多个临时文件,每个文件按照当前单个二级caching文件的方式串行化线程访问。

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

谢谢,雷蒙德。

是的,程序可以从不同的线程多次打开相同的文件。 尽管如此,您仍然希望避免在写入文件的同时从文件中读取数据。 您可以使用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); 

换句话说,除了第五个参数以外,所有参数都是一样的。

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

更新#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

你可以这样做…

具有读/写访问权限的第一个线程首先必须创建文件:

 FileHandle := CreateFile( PChar(FileName), GENERIC_READ or GENERIC_WRITE, FILE_SHARE_READ, nil, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); 

具有只读访问权限的第二线程然后打开相同的文件:

  FileHandle := CreateFile( PCHar(FileName), GENERIC_READ, FILE_SHARE_READ + FILE_SHARE_WRITE, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); 

我没有测试是否与…一起工作

 FILE_ATTRIBUTE_TEMPORARY, FILE_FLAG_DELETE_ON_CLOSE 

属性…

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

要么你不需要在不同的线程中使用相同的文件,或者你需要某种序列化。

否则,你只是为自己的心情设置自己的道路。