使用'不安全'线程函数是否安全?

And*_*and 8 delphi multithreading thread-safety critical-section interlocked-increment

请原谅我略带幽默的头衔.我在其中使用了两个不同的"安全"一词(显然).

我对线程很陌生(好吧,我已经使用了多年的线程,但只有非常简单的形式).现在我面临着编写某些算法的parallal实现的挑战,并且线程需要处理相同的数据.考虑以下新手错误:

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;
Run Code Online (Sandbox Code Playgroud)

初学者可能希望上面的代码显示消息20000000.实际上,首先value是等于0,然后inc20000000时间.但是,由于该inc过程不是'原子',两个线程将发生冲突(我猜这inc有三件事:它读取,它递增,并保存),因此很多incs将被有效"丢失".我从上面的代码中得到的典型值是10030423.

最简单的解决方法是使用InterlockedIncrement而不是Inc(在这个愚蠢的例子中会慢很多,但这不是重点).另一种解决方法是将inc内部置于一个关键部分(是的,在这个愚蠢的例子中也会非常慢).

现在,在大多数真实算法中,冲突并不常见.事实上,它们可能非常罕见.我的一个算法创建了DLA分形,我不时的变量之一inc是吸附粒子的数量.这里的冲突是非常罕见的,更重要的是,我真的不在乎变量总和是20000000,2000000,20000319还是19999496.因此,它很容易使用InterlockedIncrement或关键部分,因为它们只是膨胀代码并使它(略微)慢到没有(据我所知)的好处.

但是,我的问题是:冲突的后果是否比增量变量的稍微"不正确"值更严重?例如,程序会崩溃吗?

诚然,这个问题可能看起来很可笑,因为,毕竟,使用的成本InterlockedIncrement,而不是inc是相当低(在很多情况下,但不是所有!),所以它是(也许)笨不是为了稳妥起见.但是我也觉得知道这在理论水平上是如何起作用会很好,所以我仍然认为这个问题非常有趣.

Dav*_*nan 10

您的程序不会因为仅用作计数的整数增量的竞争而崩溃.所有可能出错的是你没有得到正确的答案.显然,如果你使用整数作为数组的索引,或者它可能是一个指针,那么你可能会遇到问题.

除非你非常频繁地增加这个值,否则很难想象互锁增量对于你注意到性能差异来说是昂贵的.

更有效的方法是让每个线程保持自己的私有计数.然后在计算结束时加入线程时对所有单个线程计数求和.这样你就可以获得两全其美.对增量和正确答案没有争论.当然,您需要采取措施确保您不会被错误的共享所困扰.