Подтвердить что ты не робот

Доступ к одному файлу с несколькими потоками

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

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

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

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

  • Возможно ли, чтобы окна одновременно обращались к одному файлу из разных потоков?
  • Если да, то как вы предоставляете эту способность? Я попытался создать временный файл и снова открыть файл, чтобы предоставить два дескриптора файлов, но второй открыть не удается.

Здесь создаются:

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 записывается, но я рад глобально сериализовать/перемежать операции чтения и записи, если я могу выполнять одновременные чтения.

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

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

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

Спасибо, Рэймонд.

4b9b3361

Ответ 1

Да, программа может открывать один и тот же файл несколько раз из разных потоков. Тем не менее, вы захотите избежать чтения из файла в то же время, когда будете писать. Вы можете использовать TMultiReadExclusiveWriteSynchronizer для управления доступом ко всему файлу. Он менее сериализован, чем, скажем, критический раздел. Для более детального контроля взгляните на LockFileEx, чтобы контролировать доступ к определенным регионам файла по мере необходимости. При письме запрашивайте эксклюзивный замок; при чтении - общий замок.

Что касается кода, который вы опубликовали, указание File_Share_Write в начальных флажках обмена означает, что все последующие открытые операции также должны совместно использовать файл для записи. Цитирование из документации:

Если этот флаг не указан, но файл или устройство было открыто для доступа к записи или имеет сопоставление файлов с доступом к записи, функция не работает.

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

Задание File_Flag_Delete_On_Close означает, что все последующие открытые запросы должны совместно использовать файл для удаления. Документация снова:

Последующие открытые запросы на сбой файла, если не указан режим обмена FILE_SHARE_DELETE.

Затем, поскольку второй открытый запрос разделяет файл для удаления, все остальные открытые дескрипторы должны также делиться им для удаления. Документация:

Если существуют существующие открытые дескрипторы файла, вызов завершается с ошибкой, если они не были открыты с помощью режима FILE_SHARE_DELETE share.

Суть в том, что либо кто-то делится одинаково, либо никто вообще не делится.

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

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

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

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

Существует три проекта:

InProcessThreadShareTest - протестируйте созданный и клиентский поток.
InProcessThreadShareTest.cpp Snippet @gist.github

SharedFileHost - создайте хост, который работает в течение 1 минуты и обновляет файл.
SharedFileClient. Создайте клиента, который выполняется в течение 30 секунд и опроса файла.
SharedFileHost.cpp и SharedFileClient.cpp Snippet @gist.github

Все эти проекты предполагают, что местоположение C:\data\tmp\sharetest.txt является творческим и доступным для записи.


Update

Учитывая ваш сценарий, звучит так, будто вам нужен очень большой кусок памяти. Вместо того, чтобы играть в системный кеш, вы можете использовать AWE для доступа к более чем 4 ГБ памяти, хотя вам нужно будет отображать фрагменты за раз. Это должно охватывать ваш сценарий L2, поскольку вы хотите обеспечить использование физической памяти.

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

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

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


Начальные

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

Управление файлами с памятью в Win32 @MSDN

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

CreateMutex @MSDN

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

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

Ответ 3

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

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

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

В потоке Sencond с доступом только для чтения открывается тот же файл:

  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

атрибуты...

Ответ 4

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

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

В противном случае вы просто настроитесь на страдание по дороге.