是否可以删除 Objective-C++ 中的 dispatch_once?
Is it possible to remove dispatch_once in Objective-C++?
自 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++ 时?
通常,Objective-C++ 允许将 Objective-C-对象和代码与 C++ 对象和代码混合,是一种不同于 "pure" C++11 的语言。因此,我不认为 C++11 保证的一切在 Objectiver-C++ 的混合世界中自动得到保证。而且我一直在花一些时间调查苹果的文档是否在 Objective-C++.
中也给出了对静态局部变量甚至块变量的特定保证
因为我没有找到关于此的声明,所以我尝试在创建对象时引入竞争条件,其中一个是建议的 "new style",即使用静态局部变量,一个是 "old style" 与 dispatch_once
和一个 "real" 竞争条件 "notOnlyOnce" 忽略任何同步(只是为了确保代码实际上引入了竞争条件)。
测试表明 "new style" 和 "old style" 似乎都是线程安全的,而 "notOnlyOnce" 显然不是。不幸的是,这样的测试只能证明 "new style" 会产生竞争条件,但不能证明永远不会有竞争条件。但是由于 "new style" 和 "old style" 的行为相同,但是 "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.
所以不清楚是或否,但我希望它能以某种方式提供帮助。
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 [=11=], __ZGVZL18GetObjectCppStaticvE6object(%rip)
jne LBB2_3
## BB#1:
leaq __ZGVZL18GetObjectCppStaticvE6object(%rip), %rdi
callq ___cxa_guard_acquire
cmpl [=11=], %eax
je LBB2_3
## BB#2:
movl , %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
.
之前
自 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++ 时?
通常,Objective-C++ 允许将 Objective-C-对象和代码与 C++ 对象和代码混合,是一种不同于 "pure" C++11 的语言。因此,我不认为 C++11 保证的一切在 Objectiver-C++ 的混合世界中自动得到保证。而且我一直在花一些时间调查苹果的文档是否在 Objective-C++.
中也给出了对静态局部变量甚至块变量的特定保证因为我没有找到关于此的声明,所以我尝试在创建对象时引入竞争条件,其中一个是建议的 "new style",即使用静态局部变量,一个是 "old style" 与 dispatch_once
和一个 "real" 竞争条件 "notOnlyOnce" 忽略任何同步(只是为了确保代码实际上引入了竞争条件)。
测试表明 "new style" 和 "old style" 似乎都是线程安全的,而 "notOnlyOnce" 显然不是。不幸的是,这样的测试只能证明 "new style" 会产生竞争条件,但不能证明永远不会有竞争条件。但是由于 "new style" 和 "old style" 的行为相同,但是 "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.
所以不清楚是或否,但我希望它能以某种方式提供帮助。
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 [=11=], __ZGVZL18GetObjectCppStaticvE6object(%rip)
jne LBB2_3
## BB#1:
leaq __ZGVZL18GetObjectCppStaticvE6object(%rip), %rdi
callq ___cxa_guard_acquire
cmpl [=11=], %eax
je LBB2_3
## BB#2:
movl , %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
.