Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/multithreading/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Multithreading 方法指针分配是线程安全的吗? 例子:_Multithreading_Delphi - Fatal编程技术网

Multithreading 方法指针分配是线程安全的吗? 例子:

Multithreading 方法指针分配是线程安全的吗? 例子:,multithreading,delphi,Multithreading,Delphi,假设我有以下线程(请不要考虑本例的线程上下文执行方法中使用的内容,这只是为了解释): 问题: 更改可以随时从另一个线程的上下文从工作线程调用的方法是否安全?执行上述示例中所示的操作是否安全 我不确定这是否绝对安全,至少因为方法指针实际上是一对指针,我不知道是否可以将其视为原子操作。不,它不是线程安全的,因为该操作永远不会是“原子的”。TNotifyEvent由两个指针组成,这些指针永远不会同时被分配:一个被分配,另一个被分配 为TNotifyEvent赋值生成的32位汇编程序由两条不同的汇编程序

假设我有以下线程(请不要考虑本例的线程上下文执行方法中使用的内容,这只是为了解释):

问题: 更改可以随时从另一个线程的上下文从工作线程调用的方法是否安全?执行上述示例中所示的操作是否安全


我不确定这是否绝对安全,至少因为方法指针实际上是一对指针,我不知道是否可以将其视为原子操作。

不,它不是线程安全的,因为该操作永远不会是“原子的”。
TNotifyEvent
由两个指针组成,这些指针永远不会同时被分配:一个被分配,另一个被分配

TNotifyEvent
赋值生成的32位汇编程序由两条不同的汇编程序指令组成,如下所示:

MOV [$00000000], Object
MOV [$00000004], MethodPointer
如果它是一个单指针,那么您将有一些选项,因为该操作是原子的:您的选项取决于CPU的内存模型有多强:

  • 如果CPU支持“顺序一致性”模型,那么在写入内存后发生的任何读取都将看到新值,这是有保证的。如果是这样的话,你可以简单地写下你的值,不需要内存障碍或者使用
    联锁的
    方法
  • 如果CPU对重新排序存储和加载更为宽松,那么您需要一个“内存屏障”。如果是这样的话,最简单的解决方法就是使用
不幸的是,我不知道当前英特尔CPU的内存模型有多强。有一些间接证据表明可能会进行一些重新排序,建议使用
联锁的
,但我还没有看到英特尔的明确声明说其中之一

证据:

  • 现代CPU使用“预取”——这自动意味着某种程度的加载/存储重新排序
  • SSE介绍了处理CPU缓存的具体说明

除了超过寄存器大小之外,还涉及两个操作。一种检查,然后执行。要最小化,请创建一个局部变量并使用它。但无论如何,这仍然不是100%线程安全的

var
  LNotify: TNotifyEvent;
begin
  ...
  LNotify := FOnNotify;
  if Assigned(LNotify) then
    LNotify(Self);
end;

与其这样做,不如始终将其赋值,并使用32位变量,而不是integer或native integer类型。然后为整数指定一个非零值或零值。如果整数值不为零,则调用该事件,否则不调用该事件。这是一个简单的解决办法。@bobshacks,这在多cpu(或多核)系统上是不安全的,除非每次分配sentinel整数时都会造成内存障碍。在没有障碍的情况下,一些CPU将在分配后的一段时间内使用缓存中的“过时”数据。您需要
InterlockedXYZ
例程,但它可以工作。True。使用InterlockedExchange功能进行分配。在这种情况下,最好是联锁增量和联锁增量。另一个想法是使指针指向方法指针。因此,将其设置为表单中的变量,然后将指向该变量的指针传递给线程,并使用原子函数自然地执行此操作。那还是一个指针。@bobshacks,谢谢!我想我能应付那种情况。我问这个问题只是出于好奇(事实上,我不会真的这么做),希望它也能对其他人有所帮助,因为方法分配不是那么明显,人们可以忽略这个风险。@CosminPrund这对英特尔上的德尔福来说其实不是问题。例如,由于这一点,双重检查锁定在英特尔上的Delphi上运行良好。英特尔x86和x64有一个强大的内存模型,而Delphi不会优化或缓存全局可见的任何数据(即非局部变量的数据)的读写。这就是我的想法。我只是想知道,编译器是否会以某种方式“神奇地”将它们视为一个指针。如果后面没有这样的“单指针编译器魔法”,那么它当然是不安全的。我可以自己(懒我)在反汇编中检查这个。谢谢确认![+1&accept]内置的
OnTerminate
事件是否具有相同的风险?当然,每个线程生命周期只调用一次。但是AFAICS RTL不保护它的赋值。@Sertac,
OnTerminate
方法是通过
Synchronize
调用的,至少在Delphi 2009中是这样,所以它实际上是在主线程的上下文中执行的,所以没有风险。@TLama-我的问题不是调用指定的方法,这是关于分配给通知事件的。@SertacAkyuz,为什么
OnTerminate
的行为会有所不同?这仍然是一个两指针的交易,编译器以同样的方式分配它。风险较低,因为它只被调用一次,然后通过
Synchronize
调用;尽管如此,如果第三个线程(不是主线程,也不是终止线程)正在分配它,风险是完全相同的。
LNotify:=FOnNotify
不是原子的。所以,不要掷骰子。@David-我想这是为了在“已分配”测试之后和调用过程之前保护分配更改。@Sertac这还不够,是吗?@Sertac,除了这一位非常不敏感。线程安全是非此即彼的事情。代码不能是90%线程safe@BasePointerDelphi全局变量、记录字段和对象字段总是易变的。这个答案中的代码是毫无意义的,因为数据有两个指针宽,因此无法进行原子访问。
MOV [$00000000], Object
MOV [$00000004], MethodPointer
var
  LNotify: TNotifyEvent;
begin
  ...
  LNotify := FOnNotify;
  if Assigned(LNotify) then
    LNotify(Self);
end;