Доступ к одному файлу с несколькими потоками
Мне нужно получить доступ к файлу одновременно с несколькими потоками. Это необходимо сделать одновременно, без сериализации потоков по причинам производительности.
Файл, в частности, был создан с атрибутом "временного" файла, который побуждает окна хранить файл в системном кеше. Это означает, что большую часть времени чтение файла обычно не приближается к диску, но будет считывать часть файла из системного кеша.
Возможность одновременного доступа к этому файлу значительно улучшит производительность некоторых алгоритмов в моем коде.
Итак, здесь есть два вопроса:
- Возможно ли, чтобы окна одновременно обращались к одному файлу из разных потоков?
- Если да, то как вы предоставляете эту способность? Я попытался создать временный файл и снова открыть файл, чтобы предоставить два дескриптора файлов, но второй открыть не удается.
Здесь создаются:
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 не будет с нами в ближайшее время: - (
Спасибо,
Рэймонд.
Ответы
Ответ 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
Мне нужно получить доступ к файлу одновременно с несколькими потоками. Это необходимо сделать одновременно, без сериализации потоков по причинам производительности.
Либо вам не нужно использовать один и тот же файл в разных потоках, либо вам нужна сериализация.
В противном случае вы просто настроитесь на страдание по дороге.