Вопрос

Я создаю игрушечную базу данных на C#, чтобы узнать больше о компиляторе, оптимизаторе и технологии индексирования.

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

Вот несколько вариантов и проблемы, с которыми я столкнулся при каждом из них:

  1. Использовать System.IO.FileStream и BeginRead метод

    Но позиция в файле не является аргументом для BeginRead, это собственность FileStream (устанавливается через Seek метод), поэтому я могу выдать только один запрос за раз и должен заблокировать поток на это время.(Или я?В документации неясно, что произойдет, если я буду удерживать замок только между Seek и BeginRead звонит, но отпустил его перед звонком EndRead.Кто-нибудь знает?) Я знаю, как это сделать, просто не уверен, что это лучший способ.

  2. Кажется, есть другой путь, основанный на System.Threading.Overlapped структуру и P\Invoke в ReadFileEx функция в kernel32.dll.

    К сожалению, примеров не хватает, особенно на управляемых языках.Этот путь (если его вообще можно заставить работать), по-видимому, также предполагает ThreadPool.BindHandle метод и потоки завершения ввода-вывода в пуле потоков.У меня складывается впечатление, что это разрешенный способ справиться с этим сценарием в Windows, но я этого не понимаю и не могу найти точку входа в документацию, которая была бы полезна для непосвященных.

  3. Что-то другое?

  4. В комментарии Джейкоб предлагает создать новый FileStream за каждое прочтение в полете.

  5. Считайте весь файл в память.

    Это сработало бы, если бы база данных была небольшой.Кодовая база небольшая, и есть много других недостатков, но сама база данных — нет.Я также хочу быть уверенным, что выполняю всю бухгалтерию, необходимую для работы с большой базой данных (что, как оказывается, составляет огромную часть сложности:разбиение на страницы, внешняя сортировка, ...) и я беспокоюсь, что случайно обмануть их будет слишком легко.

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

Разъяснение того, почему я подозреваю решение 1:удержание одной блокировки на всем пути от BeginRead до EndRead означает, что мне нужно заблокировать любого, кто хочет инициировать чтение только потому, что выполняется другое чтение.Это кажется неправильным, потому что поток, инициирующий новое чтение, может (в общем) иметь возможность выполнить дополнительную работу до того, как результаты станут доступны.(На самом деле, просто написание этого побудило меня придумать новое решение, которое я назвал новым ответом.)

Это было полезно?

Решение

Мы написали небольшой слой вокруг портов завершения ввода-вывода, состояния ReadFile и GetQueuedCompletion в C++/CLI, а затем выполнили обратный вызов в C# после завершения операции.Мы выбрали этот маршрут вместо BeginRead и шаблона асинхронной операции C#, чтобы обеспечить больший контроль над буферами, используемыми для чтения из файла (или сокета).Это был довольно большой прирост производительности по сравнению с чисто управляемым подходом, при котором новый byte[] выделяется в куче при каждом чтении.

Кроме того, в сети есть гораздо более полные примеры использования портов завершения ввода-вывода на C++.

Другие советы

Я не уверен, что понимаю, почему вариант 1 вам не подойдет.Имейте в виду, что вы не можете иметь два разных потока, пытающихся использовать один и тот же FileStream одновременно — это определенно вызовет у вас проблемы.BeginRead/EndRead предназначен для того, чтобы ваш код продолжал выполняться во время выполнения потенциально дорогостоящей операции ввода-вывода, а не для включения какого-либо многопоточного доступа к файлу.

Поэтому я бы посоветовал вам поискать, а затем начать чтение.

Что, если вы сначала загрузите ресурс (данные файла или что-то еще) в память, а затем поделитесь им между потоками?Так как это небольшая БД.- Вам не придется решать столько проблем.

Используйте подход №1, но

  1. Когда поступит запрос, возьмите замок А.Используйте его для защиты очереди ожидающих запросов на чтение.Добавьте его в очередь и верните новый асинхронный результат.Если это приведет к первому добавлению в очередь, перед возвратом вызовите шаг 2.Прежде чем вернуться, снимите блокировку A.

  2. Когда чтение завершится (или будет вызвано на шаге 1), возьмите блокировку A.Используйте его для защиты вывода запроса на чтение из очереди.Возьмите замок Б.Используйте его, чтобы защитить Seek -> BeginRead -> EndRead последовательность.Разблокируйте замок Б.Обновите асинхронный результат, созданный на шаге 1 для этой операции чтения.(После завершения операции чтения вызовите ее еще раз.)

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top