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

gr8qqesn  于 2022-11-23  发布在  其他
关注(0)|答案(5)|浏览(387)

我需要用多个线程同时访问一个文件。这需要同时完成,出于性能原因,没有线程序列化。
特别是这个文件是用“临时”文件属性创建的,它鼓励windows将文件保存在系统缓存中。这意味着大多数时候文件读取不会接近磁盘,而是从系统缓存中读取文件的一部分。
能够并发访问此文件将显著提高代码中某些算法的性能。
所以,这里有两个问题:

  1. windows是否可以从不同的线程并发访问同一个文件?
    1.如果是这样,您如何提供此功能?我尝试创建临时文件并再次打开该文件以提供两个文件句柄,但第二次打开不成功。
    下面是创建:
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系统缓存中Win 64意味着我可以有大量的内存供系统缓存使用,这样用于保存L2缓存的内存就可以计入进程内存。
多个(可能是许多)线程希望同时访问二级缓存中包含的信息。目前,访问是串行化的,这意味着一个线程可以读取它的数据,而大多数(或其余)线程被阻止,等待该操作的完成。
L2缓存文件确实会被写入,但我很乐意全局串行化/交错读取和写入类型的操作,只要我可以执行并发读取。
我知道存在严重的潜在线程并发问题,我也知道在其他上下文中有几十种方法可以解决这个问题。我有一个特定的上下文,我正在尝试确定是否有一种方法可以允许在文件和同一进程中进行并发线程读访问。
我考虑过的另一种方法是将二级缓存拆分为多个临时文件,其中每个文件以当前单个二级缓存文件的方式串行化线程访问。
是的,这种有点绝望的方法是因为64位 Delphi 不会很快与我们在一起:

vq8itlhq

vq8itlhq1#

是的,一个程序可以从不同的线程多次打开同一个文件。但是,您可能希望避免在写入文件的同时阅读文件。您可以使用TMultiReadExclusiveWriteSynchronizer来控制对整个文件的访问。它的序列化程度不如临界区。要实现更精细的控制,看一下LockFileEx,它可以根据需要控制对文件特定区域的访问。当阅读时,共享锁。
对于您发布的代码,在初始共享标志中指定File_Share_Write意味着所有后续的打开操作也必须共享该文件以进行写入。
如果未指定此标志,但文件或设备已打开以进行写访问,或者具有带写访问权限的文件Map,则函数将失败。
您的第二个打开请求是说,它不希望在该句柄保持打开时允许其他任何人写入文件。由于已经有另一个句柄打开并 * 确实 * 允许写入,因此第二个请求无法完成。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);

换句话说,除了第五个参数之外,所有参数都相同。
这些规则适用于在“同一”线程上进行的两次打开尝试以及来自不同线程的尝试。

ui7jx7zq

ui7jx7zq2#

更新#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以上的内存,而不是使用系统缓存,尽管您需要一次Map多个部分。这应该涵盖您的L2方案,因为您希望确保使用物理内存。
Address Windowing Extensions @ MSDN
使用分配用户物理页和虚拟分配来保留内存。
AllocateUserPhysicalPages Function (Windows) @ MSDN
VirtualAlloc Function (Windows) @ MSDN

姓名首字母缩写

假设您正在使用标志FILE_FLAG_DELETE_ON_CLOSE,是否有任何理由不考虑使用内存Map文件?
Managing Memory-Mapped files in Win32 @ MSDN
从我在CreateFile语句中看到的情况来看,您似乎希望跨线程或跨进程共享数据,内存Map文件允许您在所有会话中使用相同的逻辑文件名。另一个好处是您可以在所有会话中Map视图并安全地锁定Map文件的部分。如果你有一个严格的服务器和N个客户端的场景,它应该很容易实现。如果你有一个客户端可能是打开服务器的情况,你可能希望考虑使用一些其他的机制来确保只有一个客户端可以首先启动服务文件(也许通过一个全局互斥体)。
CreateMutex @ MSDN
如果您只需要单向传输数据,也许可以使用命名管道。

  • (edit)这最适合于1台服务器对1台客户端。*

Named Pipes (Windows) @ MSDN

chhqkbe1

chhqkbe13#

你可以这样做...
具有读/写访问权限的第一个线程必须首先创建文件:

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

属性⋯

50pmv0ei

50pmv0ei4#

  • 我需要使用多个线程同时访问一个文件。这需要同时完成,出于性能原因,不需要进行线程序列化。*

要么不需要在不同的线程中使用同一个文件,要么需要某种序列化。
否则,你只会让自己在未来的道路上心痛。

xam8gpfp

xam8gpfp5#

当且仅当请求读取的字节数为|写入等于或小于CPU的数据总线的大小(位宽);现在通常是64位,或者8字节。我想这可以被称为“同步CPU访问”对象。

相关问题