Безопасно ли использовать «Небезопасные» функции потоков?

Прошу прощения за мой слегка юмористический заголовок. Я использую в нем два разных определения слова «безопасный» (очевидно).

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

const
  N = 2;

var
  value: integer = 0;    

function ThreadFunc(Parameter: Pointer): integer;
var
  i: Integer;
begin
  for i := 1 to 10000000 do
    inc(value);
  result := 0;
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  threads: array[0..N - 1] of THandle;
  i: Integer;
  dummy: cardinal;
begin

  for i := 0 to N - 1 do
    threads[i] := BeginThread(nil, 0, @ThreadFunc, nil, 0, dummy);

  if WaitForMultipleObjects(N, @threads[0], true, INFINITE) = WAIT_FAILED then
    RaiseLastOSError;

  ShowMessage(IntToStr(value));

end;

Новичок может ожидать, что приведенный выше код отобразит сообщение 20000000 . Действительно, первое значение равно 0 , а затем мы inc это 20000000 раз. Однако, поскольку процедура inc не является «атомарной», два потока будут конфликтовать (я предполагаю что inc делает три вещи: читает, увеличивает и сохраняет), поэтому многие из inc будут фактически «потеряны». A типичное значение, которое я получаю из приведенного выше кода, составляет 10030423 .

Простейший обходной путь - использовать InterlockedIncrement вместо Inc (что будет намного медленнее в этом глупом примере, но дело не в этом). Другой обходной путь - поместить inc в критическую секцию (да, в этом глупом примере это тоже будет очень медленным).

В большинстве реальных алгоритмов конфликты встречаются не так часто. На самом деле они могут быть очень необычными. Один из моих алгоритмов создает фракталов DLA , и одна из переменных, которые я inc то и дело, - это количество адсорбированных частиц. Конфликты здесь очень редки, и, что более важно, меня действительно не волнует, дает ли переменная сумма до 20000000, 20000008, 20000319 или 19999496. Таким образом, возникает соблазн , а не использовать InterlockedIncrement или критические секции, так как они просто раздувают код и делают его (незначительно) медленнее, а не (насколько я понимаю) преимуществом.

Однако у меня вопрос: могут ли быть более серьезные последствия конфликтов, чем слегка «неправильное» значение увеличивающейся переменной? Может ли, например, произойти сбой программы?

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

8
задан Andreas Rejbrand 7 January 2012 в 13:13
поделиться

0 ответов

Другие вопросы по тегам:

Похожие вопросы: