【问题标题】:Mixing Atomic operation with non atomic operation混合原子操作和非原子操作
【发布时间】:2019-01-09 07:12:48
【问题描述】:

在我们的delphi源代码中:

class function TNetEncoding.GetBase64Encoding: TNetEncoding;
var
  LEncoding: TBase64Encoding;
begin
  if FBase64Encoding = nil then
  begin
    LEncoding := TBase64Encoding.Create;
    if AtomicCmpExchange(Pointer(FBase64Encoding), Pointer(LEncoding), nil) <> nil then
      LEncoding.Free
{$IFDEF AUTOREFCOUNT}
    else
      FBase64Encoding.__ObjAddRef
{$ENDIF AUTOREFCOUNT};
  end;
  Result := FBase64Encoding;
end;

但我不明白,它们混合了 原子操作AtomicCmpExchange(Pointer(FBase64Encoding), Pointer(LEncoding), nil)非原子操作,例如 if FBase64Encoding = nil thenResult := FBase64Encoding;

是不是搞错了?

【问题讨论】:

  • 不,这不是一个错误。它是双重检查锁定的一种变体。但是,您允许线程推测性地创建单例,而不是锁定。如果两个线程创建对象,只有第一个成功,第二个销毁他们的新对象。
  • @DavidHeffernan: 但是如果一个线程正在执行 AtomicCmpExchange(Pointer(FBase64Encoding), Pointer(LEncoding), nil) 并且在完全相同的时刻另一个正在执行如果 FBase64Encoding = nil 并且此时只有一半指针的字节被写入 FBase64Encoding (在 64 位上,假设指针使用的总共 8 个字节中只写入了 4 个字节)所以 FBase64Encoding 不会是 nil 但也不会指向好的位置?跨度>
  • 这解释了你关于 CocoaPointerConst 的 QP 报告 :-)
  • @DaveNottage 是的 :)

标签: delphi firemonkey


【解决方案1】:

在 cmets 中,您明确表示您担心未受保护的内存操作可能会撕裂。撕裂是指读取线程在部分写入时读取变量。

一般来说,这是一个有效的问题,但在这种情况下不会发生撕裂。原因是对齐的内存访问保证不会撕裂。当内存操作对齐时,就像这个一样,读取器无法读取部分写入的变量。这通常由硬件总线在单个高速缓存行中串行化所有内存访问来保证。

所以,不,这不是错误,代码是正确的。

代码本身用于懒惰地创建单例。以线程安全方式执行此操作的常用技术是双重检查锁定。此代码使用了一种避免锁定的替代技术。相反,代码可能允许多个线程推测性地创建单例。如果多个线程成功创建对象,则第一个成功的线程获胜,其他线程销毁其实例并使用获胜线程创建的实例。

如果创建其他实例然后销毁它们是良性的,那么无锁方法效果很好。但情况并非总是如此。例如,创建实例的多个副本可能过于昂贵。在这种情况下,基于锁的方法会更好。

【讨论】:

  • “当内存操作对齐时,就像这个一样,读取器无法读取部分写入的变量。” ...嗯不确定,因为它还取决于要读取的内存大小。在 int32 对齐变量上是的,我看到我无法读取部分整数,但它在 int64 变量上是否相同(尚未测试)和在 iOS 64 位(指针是 int64)上是否相同?
  • 事实上 64 位对齐的访问即使在 32 位架构上也无法撕裂,当然也不能在 64 位架构上撕裂
猜你喜欢
  • 1970-01-01
  • 2017-11-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-02
相关资源
最近更新 更多