Прошу прощения за мой слегка юмористический титул.Я использую два разных определения слова «безопасный» в нем (очевидно).
Я довольно плохо знаком с потоками (ну, я использовал потоки много лет, но только в очень простых формах).Теперь я столкнулся с проблемой написания параллельных реализаций некоторых алгоритмов, и потоки должны работать с одними и теми же данными.Рассмотрим следующую ошибку новичка:
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
.Действительно, сначала value
равно 0
, а затем мы inc
это 20000000
раз.Однако, поскольку процедура inc
не является «атомарной», два потока будут конфликтовать (я полагаю, что inc
делает три вещи: читает, увеличивает и сохраняет), и поэтому большая часть inc
s будут эффективно «потеряны».Типичное значение, которое я получаю из приведенного выше кода: 10030423
.
Самый простой обходной путь - использовать InterlockedIncrement
вместо Inc
(который будетнамного медленнее в этом глупом примере, но это не главное).Другой обходной путь - поместить inc
в критическую секцию (да, это также будет очень медленно в этом глупом примере).
Теперь, в большинстве реальных алгоритмов, конфликты не так распространены.На самом деле, они могут быть очень необычными.Один из моих алгоритмов создает DLA-фракталы , и одна из переменных, которые я inc
время от времени - это число адсорбированных частиц.Конфликты здесь очень редки, и, что более важно, мне действительно все равно, если переменная суммирует до 20000000, 20000008, 20000319 или 19999496. Таким образом, не заманчиво использовать InterlockedIncrement
иликритические разделы, так как они просто раздувают код и делают его (незначительно) медленнее или не приносят (насколько я вижу) выгоды.
Однако мой вопрос: могут ли быть более серьезные последствия конфликтов, чемнемного «неправильное» значение инкрементной переменной?Может ли программа аварийно завершиться, например?
По общему признанию, этот вопрос может показаться глупым, потому что, в конце концов, стоимость использования InterlockedIncrement
вместо inc
довольно низкая (во многих случаях, но не во всех!), и поэтому (возможно) глупо не играть безопасно.Но я также чувствую, что было бы хорошо узнать, как это действительно работает на теоретическом уровне, поэтому я все еще думаю, что этот вопрос очень интересен.