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

时间:2009-10-27 17:39:41

标签: windows multithreading delphi file-io

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

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

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

所以,这里有两个问题:

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

    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 / L2缓存结构的内存中保留尽可能多的空间数据库。 L1已经存在。 L2作为一个“临时”文件存在,它保留在Windows系统缓存中(这有点像一个肮脏的技巧,但在某种程度上绕过win32内存限制)。 Win64意味着系统缓存可以使用大量内存,因此用于保存L2缓存的内存确实会计入进程内存。

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

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

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

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

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

    谢谢, 雷蒙德。

5 个答案:

答案 0 :(得分:18)

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

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

  

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

你的第二个打开请求是说它不希望任何其他人在该句柄保持打开时被允许写入该文件。由于已经有另一个句柄打开 允许写入,第二个请求无法完成。 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);

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

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

答案 1 :(得分:6)

更新#2

我在C中写了一些测试项目试图弄明白 - 虽然Rob Kennedy在我不在的时候打败了我。他概述了这两种情况,包括交叉过程。如果其他人希望看到这一点,这里有一个链接。

SharedFileTests.zip (VS2005 C++ Solution) @ meklarian.com

有三个项目:

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

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

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


更新

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

Address Windowing Extensions @ MSDN

使用AllocateUserPhysicalPages和VirtualAlloc保留内存。

AllocateUserPhysicalPages Function (Windows) @ MSDN
VirtualAlloc Function (Windows) @ MSDN


初始

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

Managing Memory-Mapped files in Win32 @ MSDN

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

CreateMutex @ MSDN

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

Named Pipes (Windows) @ MSDN

答案 2 :(得分:2)

你可以这样做......

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

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

...属性

答案 3 :(得分:1)

我需要与多个线程同时访问文件。由于性能原因,这需要同时完成,而不进行线程序列化。

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

否则,你只是让自己心烦意乱。

答案 4 :(得分:0)

线程 A/B 分别同时向/从同一文件写入/读取,当且仅当请求读取的字节数等于或小于数据大小时,才是完全线程安全的CPU的总线(位宽);现在通常是 64 位,或 8 个字节。我想这可以称为“同步 CPU 访问”对象。