Доступ к одному файлу с несколькими потоками - PullRequest
17 голосов
/ 27 октября 2009

Мне нужно получить доступ к файлу одновременно с несколькими потоками. Это должно быть сделано одновременно, без сериализации потоков по соображениям производительности.

Файл, в частности, был создан с атрибутом временного файла, который поощряет окна сохранять файл в системном кэше. Это означает, что большую часть времени чтение файла не будет происходить рядом с диском, но будет считывать часть файла из системного кэша.

Возможность одновременного доступа к этому файлу значительно улучшит производительность некоторых алгоритмов в моем коде.

Итак, здесь есть два вопроса:

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

Вот второе открытие:

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

Я пробовал разные комбинации флагов, но пока безуспешно. При открытии второго файла всегда происходит сбой, с сообщениями о том, что к файлу нельзя получить доступ, так как он используется другим процессом.

Редактировать:

Хорошо, еще немного информации (я надеялся не потеряться здесь в сорняках ...)

Рассматриваемый процесс - это процесс сервера Win32, работающий на WinXP 64. Он поддерживает большие пространственные базы данных и хотел бы сохранить как можно большую часть пространственной базы данных в памяти в структуре кэша L1 / L2. L1 уже существует. L2 существует как «временный» файл, который остается в системном кеше Windows (это немного грязный трюк, но несколько обходит ограничения памяти win32). Win64 означает, что у меня может быть много памяти, используемой системным кэшем, поэтому память, используемая для хранения кэша L2, действительно учитывается в памяти процесса.

Несколько (потенциально много) потоков хотят одновременно получать доступ к информации, содержащейся в кэше L2. В настоящее время доступ сериализован, что означает, что один поток получает данные для чтения, в то время как большинство (или остальные) потоков блокируются в ожидании завершения этой операции.

Файл кэша L2 действительно записывается, но я рад глобально сериализовать / перемежать операции чтения и записи, пока я могу выполнять параллельные чтения.

Я знаю, что есть неприятные потенциальные проблемы параллелизма потоков, и я знаю, что есть десятки способов обшарить эту кошку в других контекстах. У меня есть этот конкретный контекст, и я пытаюсь определить, есть ли способ разрешить одновременный доступ к чтению потока в файле и в том же процессе.

Другой подход, который я рассмотрел, состоит в том, чтобы два раза разделить кэш L2 на несколько временных файлов, где каждый файл сериализует поток доступа, как текущий текущий файл кэш-памяти L2.

И да, этот несколько отдельный подход заключается в том, что 64-битный Delphi не будет с нами в ближайшее время: - (

Спасибо, Raymond.

Ответы [ 4 ]

18 голосов
/ 28 октября 2009

Да, программа может открывать один и тот же файл несколько раз из разных потоков. Вы хотите избежать чтения из файла в то же время, когда пишете в него. Вы можете использовать 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);

Другими словами, все параметры одинаковы, кроме пятого.

Эти правила применяются к двум попыткам открытия в том же потоке , а также к попыткам из разных потоков.

6 голосов
/ 27 октября 2009

Обновление № 2

Я написал несколько тестовых проектов на C, чтобы попытаться выяснить это, хотя Роб Кеннеди опередил меня с ответом, пока меня не было. Оба условия возможны, включая перекрестный процесс, как он обрисовывает в общих чертах. Вот ссылка, если кто-то еще хотел бы увидеть это в действии.

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, чтобы иметь доступ к более чем 4 ГБ памяти, хотя вам нужно будет отображать порции за раз. Это должно охватывать ваш сценарий L2, поскольку вы хотите обеспечить использование физической памяти.

Расширения оконного адреса @ MSDN

Используйте AllocateUserPhysicalPages и VirtualAlloc для резервирования памяти.

Функция AllocateUserPhysicalPages (Windows) @ MSDN
Функция VirtualAlloc (Windows) @ MSDN


Первый

Учитывая, что вы используете флаг FILE_FLAG_DELETE_ON_CLOSE, есть ли причина, по которой вы бы не рассматривали использование вместо этого файла с отображением в памяти?

Управление отображенными в память файлами в Win32 @ MSDN

Из того, что я вижу в ваших операторах CreateFile, видно, что вы хотите обмениваться данными между потоками или между процессами, имея в виду, что один и тот же файл присутствует, когда открыты любые сеансы. Файл с отображенной памятью позволяет вам использовать одно и то же логическое имя файла во всех сеансах. Еще одним преимуществом является то, что вы можете сопоставлять представления и блокировать части сопоставленного файла с безопасностью во всех сеансах. Если у вас строгий сервер со сценарием N-client, его легко реализовать. Если у вас есть случай, когда любой клиент может быть открывающим сервером, вы можете рассмотреть возможность использования какого-либо другого механизма, чтобы гарантировать, что только один клиент сможет инициировать файл обслуживания первым (возможно, через глобальный мьютекс).

CreateMutex @ MSDN

Если вам нужна только односторонняя передача данных, возможно, вы могли бы использовать именованные каналы.
(редактировать) Лучше всего для 1 сервера на 1 клиента.

Именованные каналы (Windows) @ MSDN

2 голосов
/ 27 октября 2009

Вы можете сделать так ...

Первый поток с правами чтения / записи должен сначала создать файл:

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

атрибуты ...

1 голос
/ 27 октября 2009

Мне нужно получить доступ к файлу одновременно с несколькими потоками. Это должно быть сделано одновременно, без сериализации потоков по соображениям производительности.

Либо вам не нужно использовать один и тот же файл в разных потоках, либо вам нужна какая-то сериализация.

В противном случае вы просто настраиваетесь на душевную боль в будущем.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...