Multithreading 使用'安全吗;不安全';线程函数?
请原谅我略带幽默的标题。我对“安全”一词使用了两种不同的定义(显然) 我对线程技术相当陌生(好吧,我已经使用线程技术很多年了,但只是非常简单的形式)。现在我面临着编写一些算法的视差实现的挑战,线程需要处理相同的数据。考虑下面的新手错误:Multithreading 使用'安全吗;不安全';线程函数?,multithreading,delphi,thread-safety,critical-section,interlocked-increment,Multithreading,Delphi,Thread Safety,Critical Section,Interlocked Increment,请原谅我略带幽默的标题。我对“安全”一词使用了两种不同的定义(显然) 我对线程技术相当陌生(好吧,我已经使用线程技术很多年了,但只是非常简单的形式)。现在我面临着编写一些算法的视差实现的挑战,线程需要处理相同的数据。考虑下面的新手错误: const N = 2; var value: integer = 0; function ThreadFunc(Parameter: Pointer): integer; var i: Integer; begin for i :=
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
it20000000
次。但是,由于inc
过程不是“原子的”,两个线程将发生冲突(我猜inc
执行三件事:读取、递增和保存),因此许多inc
将实际上“丢失”。我从上面的代码中得到的典型值是10030423
最简单的解决方法是使用而不是(在这个愚蠢的示例中,这会慢得多,但这不是重点)。另一个解决方法是将inc
放在一个关键部分内(是的,在这个愚蠢的示例中,这也会非常慢)
现在,在大多数实际算法中,冲突并不常见。事实上,它们可能非常罕见。我的一个算法创建了一个变量,其中一个变量是吸附粒子的数量。这里的冲突非常罕见,更重要的是,我真的不在乎变量的总和是20000000、2000008、20000319还是19999496。因此,不使用InterlockedIncrement
或critical部分是很有诱惑力的,因为它们只会使代码膨胀,并使代码(稍微)变慢到没有(据我所见)好处
然而,我的问题是:冲突的后果会比递增变量的值稍微“不正确”更严重吗?例如,程序会崩溃吗
诚然,这个问题可能看起来很愚蠢,因为毕竟,使用
InterlockedIncrement
而不是inc
的成本相当低(在许多情况下,但不是全部!),因此(也许)不谨慎行事是愚蠢的。但是我也觉得知道这在理论层面上是如何工作的会很好,所以我仍然认为这个问题很有趣。你的程序不会因为一个只用作计数的整数增量的竞争而崩溃。唯一可能出错的是你没有得到正确的答案。显然,如果您使用整数作为数组的索引,或者它可能是指针,那么您可能会遇到问题
除非您以难以置信的频率递增该值,否则很难想象联锁递增的成本会高到足以让您注意到性能差异
更有效的方法是让每个线程维护自己的私有计数。然后在计算结束时加入线程时,求和所有单个线程计数。这样你就能两全其美。没有关于递增和正确答案的争论。当然,您需要采取措施确保自己不会被发现。谢谢您的回答。关于最后一部分,我知道这是一个愚蠢的例子,但我只是想证实我的假设,即在这种情况下可能发生的最糟糕的事情是明显错误(或“稍微偏离”)的结果。风险当然也取决于你对该计数的处理。如果它仅用于显示,这是没有问题的,但如果您将其用于迭代或其他内容,其中错误的计数可能意味着内存泄漏或访问已被删除的内存,则可能会出现问题deallocated@Andreas我们就是忍不住要扩大这个问题@大卫:也许我应该补充一点说明,我确实知道这个程序可以简化为
ShowMessage('20000000')
!还有一个+15!再次感谢!我猜投票被否决是因为这个幽默的标题。