【问题标题】:Is it possible to remove dispatch_once in Objective-C++?是否可以在 Objective-C++ 中删除 dispatch_once?
【发布时间】:2017-11-30 08:43:38
【问题描述】:

从 C++11 开始,已知本地 static 变量以线程安全的方式初始化(​​除非给出 -fno-threadsafe-statics),如指定的 in this question。这是否意味着以下众所周知的模式:

+ (NSObject *)onlyOnce {
  static NSObject *object;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    object = [[NSObject alloc] init];
  });
  return object;
}

可以换成更短的:

+ (NSObject *)onlyOnce {
  static NSObject *object = [[NSObject alloc] init];
  return object;
}

当使用 C++11 及更高版本的 C++ 语言方言将代码编译为 Objective-C++ 时?

【问题讨论】:

  • 很遗憾,没有正式的语言规范,但它可能(如果有的话)依赖于编译。选项 -fthreadsafe-statics 列在 GCC/C++ 选项下,并作为目标独立于 clangs clang 和 clang++ 驱动程序。
  • 我找不到 __has_feature,但 stackoverflow.com/q/44500144/8918119 提到了一个特殊的 GCC 函数 __cxa_guard_acquire,它用于安全的本地静态,也可以在 clang 中使用。
  • 看起来clang预处理器定义了__cpp_threadsafe_static_initgithub.com/llvm-mirror/clang/blob/…
  • GCC 太 stackoverflow.com/a/47574117/8918119 所以有人可以试试

标签: c++ objective-c c++11 thread-safety objective-c++


【解决方案1】:

TL;DR - 似乎可以以线程安全的方式使用 C++11 静态变量初始化,它具有与 dispatch_once 相同的性能特征。

按照 Stephan Lechner 的回答,我编写了最简单的代码来测试 C++ 静态初始化流程:

class Object {  
};

static Object *GetObjectCppStatic() {
  static Object *object = new Object();
  return object;
}

int main() {
  GetObjectCppStatic();
}

通过clang++ test.cpp -O0 -fno-exceptions -S 将其编译为程序集(-O0 避免内联,为-Os 生成相同的通用代码,-fno-exceptions 以简化生成的代码),表明GetObjectCppStatic 编译为:

__ZL18GetObjectCppStaticv:        ## @_ZL18GetObjectCppStaticv
  .cfi_startproc
## BB#0:
  pushq   %rbp
Lcfi6:
  .cfi_def_cfa_offset 16
Lcfi7:
  .cfi_offset %rbp, -16
  movq  %rsp, %rbp
Lcfi8:
  .cfi_def_cfa_register %rbp
  cmpb  $0, __ZGVZL18GetObjectCppStaticvE6object(%rip)
  jne LBB2_3
## BB#1:
  leaq  __ZGVZL18GetObjectCppStaticvE6object(%rip), %rdi
  callq   ___cxa_guard_acquire
  cmpl  $0, %eax
  je  LBB2_3
## BB#2:
  movl  $1, %eax
  movl  %eax, %edi
  callq   __Znwm
  leaq  __ZGVZL18GetObjectCppStaticvE6object(%rip), %rdi
  movq  %rax, __ZZL18GetObjectCppStaticvE6object(%rip)
  callq   ___cxa_guard_release
LBB2_3:
  movq  __ZZL18GetObjectCppStaticvE6object(%rip), %rax
  popq  %rbp
  retq
  .cfi_endproc

我们绝对可以看到由 libc++ ABI here 实现的___cxa_guard_acquire___cxa_guard_release。请注意,我们甚至不必向 clang 指定我们使用 C++11,因为显然这在此之前就已默认支持。

所以我们知道这两种形式都可以确保本地静态的线程安全初始化。但是性能呢?以下测试代码检查无争用(单线程)和严重争用(多线程)的两种方法:

#include <cstdio>
#include <dispatch/dispatch.h>
#include <mach/mach_time.h>

class Object {  
};

static double Measure(int times, void(^executionBlock)(), void(^finallyBlock)()) {
  struct mach_timebase_info timebaseInfo;
  mach_timebase_info(&timebaseInfo);

  uint64_t start = mach_absolute_time();
  for (int i = 0; i < times; ++i) {
    executionBlock();
  }
  finallyBlock();
  uint64_t end = mach_absolute_time();

  uint64_t timeTook = end - start;
  return ((double)timeTook * timebaseInfo.numer / timebaseInfo.denom) /
      NSEC_PER_SEC;
}

static Object *GetObjectDispatchOnce() {
  static Object *object;
  static dispatch_once_t onceToken;

  dispatch_once(&onceToken, ^{
    object = new Object();
  });

  return object;
}

static Object *GetObjectCppStatic() {
  static Object *object = new Object();
  return object;
}

int main() {
  printf("Single thread statistics:\n");
  printf("DispatchOnce took %g\n", Measure(10000000, ^{
    GetObjectDispatchOnce();
  }, ^{}));
  printf("CppStatic took %g\n", Measure(10000000, ^{
    GetObjectCppStatic();
  }, ^{}));

  printf("\n");

  dispatch_queue_t queue = dispatch_queue_create("queue", 
      DISPATCH_QUEUE_CONCURRENT);
  dispatch_group_t group = dispatch_group_create();

  printf("Multi thread statistics:\n");
  printf("DispatchOnce took %g\n", Measure(1000000, ^{
    dispatch_group_async(group, queue, ^{
      GetObjectDispatchOnce();
    });
  }, ^{
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
  }));
  printf("CppStatic took %g\n", Measure(1000000, ^{
    dispatch_group_async(group, queue, ^{
      GetObjectCppStatic();
    });
  }, ^{
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
  }));
}

在 x64 上产生以下结果:

Single thread statistics:
DispatchOnce took 0.025486
CppStatic took 0.0232348

Multi thread statistics:
DispatchOnce took 0.285058
CppStatic took 0.32596

所以到测量误差为止,似乎两种方法的性能特征是相似的,主要是由于它们都执行了double-check locking。对于dispatch_once,这发生在_dispatch_once 函数中:

void
_dispatch_once(dispatch_once_t *predicate,
    DISPATCH_NOESCAPE dispatch_block_t block)
{
  if (DISPATCH_EXPECT(*predicate, ~0l) != ~0l) {
    // ...
  } else {
    // ...
  }
}

在 C++ 静态初始化流程中,它发生在调用 ___cxa_guard_acquire 之前。

【讨论】:

    【解决方案2】:

    通常,允许将 Objective-C-Objects 和代码与 C++ 对象和代码混合的 Objective-C++ 是一种不同于“纯”C++11 的语言。因此,我不认为在 Objectiver-C++ 的混合世界中,C++11 所保证的一切都会自动得到保证。我一直在花一些时间调查苹果的文档,是否在 Objective-C++ 中也给出了对静态局部变量甚至块变量的具体保证。

    由于我没有找到对此的说明,我尝试在创建对象时引入竞争条件,一个具有建议的“新样式”,即使用静态局部变量,一个具有“旧样式”的dispatch_once,以及一个“真正的”竞争条件“notOnlyOnce”,忽略了任何同步(只是为了确保代码实际上引入了竞争条件)。

    测试表明“新样式”和“旧样式”似乎都是线程安全的,而“notOnlyOnce”显然不是。不幸的是,这样的测试可以证明“新风格”会产生竞争条件,但它不能证明永远不会存在竞争条件。但由于“新样式”和“旧样式”的行为相同,但“notOnlyOnce”在相同设置中显示了竞争条件,我们至少可以假设静态局部变量按照您的建议工作。

    请参阅以下代码和相应的输出。

    @interface SingletonClass : NSObject
    
    - (instancetype)init;
    
    @end
    
    @implementation SingletonClass
    
    - (instancetype)init {
        self = [super init];
        std::cout << "Created a singleton object" << std::endl;
        for (int i=0; i<1000000; i++) { i++; }
        return self;
    }
    
    @end
    
    @interface TestClassObjCPP : NSObject 
    
    @property (nonatomic) SingletonClass *sc;
    
    + (SingletonClass *)onlyOnceNewStyle;
    + (SingletonClass *)onlyOnceOldStyle: (TestClassObjCPP*)caller;
    + (SingletonClass *)notOnlyOnce: (TestClassObjCPP*)caller;
    
    @end
    
    @implementation TestClassObjCPP
    
    
    + (SingletonClass *)onlyOnceNewStyle {
        static SingletonClass *object = [[SingletonClass alloc] init];
        return object;
    }
    
    + (SingletonClass *)onlyOnceOldStyle: (TestClassObjCPP*)caller {
    
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            caller.sc = [[SingletonClass alloc] init];
        });
    
        return caller.sc;
    }
    
    + (SingletonClass *)notOnlyOnce: (TestClassObjCPP*)caller {
    
        if (caller.sc == nil)
            caller.sc = [[SingletonClass alloc] init];
    
        return caller.sc;
    }
    
    @end
    
    
    int main(int argc, char * argv[]) {
    
    
        @autoreleasepool {
    
            std::cout << "Before loop requesting singleton." << std::endl;
            TestClassObjCPP *caller = [[TestClassObjCPP alloc] init];
            caller.sc = nil;
            for (int i=0; i<10000; i++) {
                dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                    [TestClassObjCPP onlyOnceNewStyle];  // (1)
                    // [TestClassObjCPP onlyOnceOldStyle:caller]; // (2)
                    // [TestClassObjCPP notOnlyOnce:caller]; // (3)
                });
    
            }
            std::cout << "After loop requesting singleton." << std::endl;
    
            return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
        }
    }
    

    onlyOnceNewStyle (1) 的输出:

    Before loop requesting singleton.
    Created a singleton object
    After loop requesting singleton.
    

    onlyOnceOldStyle (2) 的输出:

    Before loop requesting singleton.
    Created a singleton object
    After loop requesting singleton.
    

    notOnlyOnce (3) 的输出:

    Before loop requesting singleton.
    Created a singleton object
    Created a singleton object
    Created a singleton object
    After loop requesting singleton.
    

    所以不清楚是或否,但我希望它在某种程度上有所帮助。

    【讨论】:

    • 也许你想在onlyOnceNewStyle 中有static SingletonClass *object
    • @A.A:哎呀,是的。复制了一个版本,我在其中测试了一些不同的东西。但输出来自带有static 的版本(否则会输出数千个“对象创建”行。
    猜你喜欢
    • 2023-03-16
    • 1970-01-01
    • 2010-12-26
    • 2011-01-15
    • 1970-01-01
    • 2011-08-08
    • 1970-01-01
    • 2019-03-28
    • 1970-01-01
    相关资源
    最近更新 更多