Когда я должен использовать критические разделы? - PullRequest
5 голосов
/ 19 марта 2011

Вот сделка. В моем приложении много потоков, которые делают одно и то же - читают определенные данные из огромных файлов (> 2 ГБ), анализируют данные и в конечном итоге записывают в этот файл.

Проблема в том, что иногда может случиться так, что один поток читает X из файла A, а второй поток пишет в X этого же файла A. Может возникнуть проблема?

Код ввода / вывода использует TFileStream для каждого файла. Я разделил код ввода / вывода на локальный (статический класс), потому что, боюсь, возникнет проблема. Поскольку он разделен, должны быть критические разделы.

Каждый приведенный ниже случай является локальным (статическим) кодом, который не создается.

Дело 1:

procedure Foo(obj:TObject);
begin ... end;

Случай 2:

procedure Bar(obj:TObject);
var i: integer;
begin
  for i:=0 to X do ...{something}
end;

Дело 3:

function Foo(obj:TObject; j:Integer):TSomeObject
var i:integer;
begin
  for i:=0 to X do
    for j:=0 to Y do
      Result:={something}
end;

Вопрос 1. В каком случае мне нужны критические секции, чтобы не возникало проблем, если> 1 поток вызывает его одновременно?

Вопрос 2. Будет ли проблема, если Поток 1 читает X (запись) из файла A, а Поток 2 записывает в X (запись) в файл A?

Когда я должен использовать критические секции? Я пытаюсь представить это своей головой, но это сложно - только одна нить :))

EDIT

Это подойдет?

{класс для каждого файла 2 ГБ}

TSpecificFile = class
  cs: TCriticalSection;
  ...
end;

TFileParser = class
  file :TSpecificFile;
  void Parsethis; void ParseThat....
end;

function Read(file: TSpecificFile): TSomeObject;
begin
  file.cs.Enter;
  try
    ...//read
  finally
    file.cs.Leave;
  end;
end;

function Write(file: TSpecificFile): TSomeObject;
begin
  file.cs.Enter;
  try
    //write
  finally
    file.cs.Leave
  end;
end;

Теперь возникнет проблема, если два потока вызовут Read:

вариант 1: тот же TSpecificFile

вариант 2: разные TSpecificFile?

Мне нужен еще один критический раздел?

Ответы [ 3 ]

7 голосов
/ 19 марта 2011

Как правило, вам нужен механизм блокировки (критические секции являются механизмом блокировки) всякий раз, когда несколько потоков могут получить доступ к общему ресурсу одновременно, и по крайней мере один из потоков будет записывать / изменять общий ресурс.
Это верно, является ли ресурс объектом в памяти или файлом на диске.
И причина, по которой необходима блокировка, заключается в том, что если операция чтения происходит одновременно с операцией записи, операция чтениявероятность получения противоречивых данных, приводящих к непредсказуемому поведению.
Стивен Чеунг упомянул конкретные аспекты платформы в отношении обработки файлов, и я не буду их здесь повторять.

В качестве примечания яХотелось бы подчеркнуть еще одну проблему параллелизма, которая может быть применима в вашем случае.

  • Предположим, один поток читает некоторые данные и начинает обработку.
  • Затем другой поток делает то же самое.
  • Оба потока определяют, что они должны записать результат впозиция X файла A.
  • В лучшем случае записываемые значения одинаковы, и один из потоков фактически ничего не делал, кроме как тратит время.
  • В худшем случае вычисление одного изпотоки перезаписываются, а результат теряется.

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

Опции

Критические секции

Да, вы можете использовать критические секции.

  • Вам нужно будет выбрать лучшую гранулярность критических разделов: по одному на весь файл или, возможно, использовать их для обозначения определенных блоков в файле.
  • Решение потребует лучшего пониманиячто делает ваше приложение, поэтому я не собираюсь отвечать за вас.
  • Просто помните о возможности взаимоблокировок:
    • Поток 1 получает блокировку A
    • Поток 2получает блокировку B
    • поток 1 желает блокировки B, но должен ждать
    • поток 2 желает блокировки A - вызывая взаимоблокировку, поскольку ни один из потоков не может снять полученную блокировку.

Я также собираюсь предложить 2 других инструмента, которые вы должны рассмотреть в своем решении.

Однопоточный

Какая шокирующая вещь!А если серьезно, если вы решили использовать многопоточность как «сделать приложение быстрее», то вы пошли по многопоточности по причине неправильно .Большинство людей, которые делают это, в действительности делают свои приложения, более трудными для написания, менее надежными и медленнее !

Это слишком распространенное заблуждение, что множественноепотоки ускоряют приложения.Если для выполнения задачи требуется X тактов - потребуется X тактов!Несколько потоков не ускоряют выполнение задач, это позволяет выполнять несколько задач параллельно.Но это может быть плохо !...

Вы описали, что ваше приложение сильно зависит от чтения с диска, анализа того, что читается, и записи на диск.В зависимости от того, насколько интенсивным является процесс анализа, вы можете обнаружить, что все ваши потоки тратят большую часть своего времени на ожидание операций дискового ввода-вывода.В этом случае множественные потоки обычно служат только для того, чтобы переместить головки дисков в дальние «углы» ваших (ummm round ) дисковых пластин.Дисковый ввод-вывод по-прежнему является узким местом, и потоки заставляют его вести себя так, как будто файлы максимально фрагментированы.

Операции с очередями

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

Таким образом, вместо потока 1:

  • Чтение позиции X из файла A
  • Анализ данных
  • Запись в позицию Y в файле A

Создать другой поток;поток FileA:

  • FileA имеет очередь инструкций
  • Когда он добирается до инструкции для чтения позиции X, он делает это.
  • Он отправляетданные в поток 1
  • поток 1 анализирует свои данные --- в то время как поток FileA продолжает обрабатывать инструкции
  • поток 1 помещает инструкцию для записи своего результата в позицию Y в конце очереди потока FileA -- пока поток FileA продолжает обрабатывать другие инструкции.
  • В конце концов поток FileA запишет данные в соответствии с требованиями Trhead 1.
5 голосов
/ 19 марта 2011

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

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

В некоторых случаях, например, отображаемые в память файлы или разреженные файлы, O / S может позволять вам записывать в другиечасти файла в то же время.Следовательно, в таких случаях ваша CS должна находиться в определенном сегменте файла.Таким образом, у вас будет коллекция CS (по одному на каждый сегмент) для каждого файла.

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

В некоторых случаях, если вы пишете в сегмент и читаете из другого сегмента, O / S может разрешить это,Однако то, будет ли это возвращать правильные данные, обычно не может быть гарантировано, потому что вы не всегда можете определить, находятся ли два сегмента файла в одном секторе диска или другие низкоуровневые операции O / S.

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

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

После примечания: то, что вы пытаетесь получить данные (чтение и запись огромных наборов данных, которыеРазмер в ГБ одновременно в сегментах) - это то, что обычно делается в базе данных.Вы должны попытаться разбить ваши файлы данных на записи базы данных.В противном случае вы либо страдаете от неоптимизированной производительности чтения / записи из-за блокировки, либо заканчиваете тем, что заново изобретаете реляционную базу данных.

3 голосов
/ 19 марта 2011

Вывод первый

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

Как я пришел к такому выводу

Сначалавсего Windows (Win 7?) позволит вам одновременно записывать в файл столько раз, сколько вы считаете нужным.Я понятия не имею, что он делает с записями, и я явно не говорю, что это хорошая идея, но я только что провел следующий тест, чтобы доказать, что Windows допускает одновременную множественную запись в один и тот же файл:

Я создал поток, который открывает файл для записи (с «share deny none») и продолжает записывать случайные вещи со случайным смещением в течение 30 секунд.Вот пастбина с кодом .

Почему TCriticalSection будет плохим

В критической секции только один поток может получить доступ к ресурсу защиты в любой момент времени.У вас есть два варианта: удерживать блокировку только на время операции чтения / записи или удерживать блокировку в течение всего времени, необходимого для обработки данного ресурса.Оба имеют серьезные проблемы.

Вот что может произойти, если поток удерживает блокировку только на время операций чтения / записи:

  • Поток 1 получает блокировку, читает данные, снимает блокировку
  • Поток 2 получает блокировку, считывает те же данные , снимает блокировку
  • Поток 1 завершает обработку, получает блокировку, записывает данные, освобождаетБлокировка
  • Поток 2 получает блокировку, записывает данные, и вот упс : Поток 2 работал со старыми данными, так как Поток 1 внес изменения в фоновом режиме!

Вот что может произойти, если поток удерживает блокировку для всей операции чтения и записи с округлым кадром:

  • Поток 1 получает блокировку и начинает читать данные
  • Поток 2 пытается получить такую ​​же блокировку, блокируется ...
  • Поток 1 завершает чтение данных, обрабатывает данные, записывает данные обратно в файл, снимает блокировку
  • Поток 2приобретает замоки начинает обрабатывать те же данные снова !

Решение очереди

Поскольку вы работаете с несколькими потоками, вы можете иметь несколько потоков, одновременно обрабатывающих данные изтот же файл, я предполагаю, что данные как-то «не зависят от контекста»: вы можете обработать третью часть файла перед обработкой первой.Это должно быть правдой, потому что если это не так, вы не можете использовать многопоточность (или ограничены 1 потоком на файл).

Перед началом обработки вы можете подготовить несколько «заданий», которые выглядятнапример:

  • Файл 'file1.raw', смещение 0, 1024 КБ
  • Файл 'file1.raw', смещение 1024, 1024 КБ.
  • ...
  • Файл 'fileN.raw', смещение 99999999, 1024 КБ

Поместить все эти "задания" в очередь.Ваши потоки исключают одно задание из очереди и обрабатывают его.Поскольку никакие два задания не перекрываются, потоки не должны синхронизироваться друг с другом, поэтому вам не нужен критический раздел.Вам нужен только критический раздел для защиты доступа к самой очереди.Windows гарантирует, что потоки могут нормально читать и записывать в / из файлов, если они придерживаются выделенного «задания».

...