Apple提供了一个CPU and GPU Synchronization示例项目,该项目展示了如何在CPU和GPU之间同步对共享资源的访问。为此,它使用存储在实例变量中的信号量:
@implementation AAPLRenderer
{
dispatch_semaphore_t _inFlightSemaphore;
// other ivars
}
然后在另一种方法中定义此信号灯:
- (nonnull instancetype)initWithMetalKitView:(nonnull MTKView *)mtkView
{
self = [super init];
if(self)
{
_device = mtkView.device;
_inFlightSemaphore = dispatch_semaphore_create(MaxBuffersInFlight);
// further initializations
}
return self;
}
MaxBuffersInFlight
的定义如下:
// The max number of command buffers in flight
static const NSUInteger MaxBuffersInFlight = 3;
最后,信号灯的用途如下:
/// Called whenever the view needs to render
- (void)drawInMTKView:(nonnull MTKView *)view
{
// Wait to ensure only MaxBuffersInFlight number of frames are getting processed
// by any stage in the Metal pipeline (App, Metal, Drivers, GPU, etc)
dispatch_semaphore_wait(_inFlightSemaphore, DISPATCH_TIME_FOREVER);
// Iterate through our Metal buffers, and cycle back to the first when we've written to MaxBuffersInFlight
_currentBuffer = (_currentBuffer + 1) % MaxBuffersInFlight;
// Update data in our buffers
[self updateState];
// Create a new command buffer for each render pass to the current drawable
id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
commandBuffer.label = @"MyCommand";
// Add completion hander which signals _inFlightSemaphore when Metal and the GPU has fully
// finished processing the commands we're encoding this frame. This indicates when the
// dynamic buffers filled with our vertices, that we're writing to this frame, will no longer
// be needed by Metal and the GPU, meaning we can overwrite the buffer contents without
// corrupting the rendering.
__block dispatch_semaphore_t block_sema = _inFlightSemaphore;
[commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> buffer)
{
dispatch_semaphore_signal(block_sema);
}];
// rest of the method
}
我在这里不明白的是线的必要性
__block dispatch_semaphore_t block_sema = _inFlightSemaphore;
为什么我必须将实例变量复制到局部变量中并用__block
标记此局部变量。如果我只是删除该局部变量,而写
[commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> buffer)
{
dispatch_semaphore_signal(_inFlightSemaphore);
}];
它似乎也能正常工作。我也尝试用__block
标记实例变量,如下所示:
__block dispatch_semaphore_t _bufferAccessSemaphore;
这可以使用Clang和似乎进行编译。但是,因为这是为了防止比赛条件,所以我想确保能正常工作。
所以问题是,Apple为什么要创建标记有__block
的本地信号量副本?直接访问实例变量的方法真的必要吗?
作为旁注,对this的回答是这样的问题:无法用__block
标记实例变量。答案是根据gcc的,但是Clang为什么不应该这样做呢?
答案 0 :(得分:3)
这里重要的语义区别是,当您直接在块中使用ivar时,该块会强烈引用self
。通过创建引用信号量的局部变量,只有信号量(而不是self
)被块捕获(通过引用),从而减少了保留周期的可能性。
对于__block
限定符,通常使用它来表示局部变量在引用块内应该是可变的。但是,由于信号量 variable 不会因对signal
的调用而发生突变,因此在这里不一定非要使用限定符。从样式的角度来看,它仍然可以起到有用的作用,因为它强调了变量的寿命和目的。
关于为什么一个ivar可以用__block
进行限定的主题,
为什么Clang不应该这样做呢?
也许正因为捕获块中的ivar意味着强烈捕获self
。使用或不使用__block
限定词,如果在一个块中使用ivar,则可能会存在保留周期的风险,因此在该条件下使用限定词不会带来额外的风险。最好使用局部变量(顺便说一下,它可以是对__weak
的{{1}}引用,就像对ivar的self
限定的引用一样容易)是显式且安全的
答案 1 :(得分:2)
我认为对于为什么要使用局部变量而不是ivar(及其对self
的隐式引用),warrenm是正确的answered your question。 +1
但是您询问了为什么在这种情况下将局部变量标记为__block
的问题。作者本可以这样做以使其意图明确(例如,表明变量将超出方法的范围)。或者他们可能出于效率考虑而这样做(例如,为什么要创建指针的新副本?)。
例如,考虑:
- (void)foo {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(4);
NSLog(@"foo: %p %@", &semaphore, semaphore);
for (int i = 0; i < 10; i++) {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"bar: %p %@", &semaphore, semaphore);
[NSThread sleepForTimeInterval:1];
dispatch_semaphore_signal(semaphore);
});
}
}
尽管它们都使用相同的信号量,但是在没有__block
的情况下,每个分派的块都将获得指向该信号量的自己的指针。
但是,如果将__block
添加到该局部semaphore
的声明中,则每个分派的块将使用位于堆上的相同指针(即{{1的地址) }}对于每个块都是相同的。
恕我直言,这在这里没有意义,因为只有一个街区被调度,但是希望这可以说明&semaphore
限定词对本地var的影响。 (显然,__block
限定词的传统用法是,如果您要更改相关对象的值,但这与此处无关。)
...无法在[gcc]中用
__block
标记实例变量...但是,如果不应该这样做,为什么Clang会允许呢?
如引用的答案所述,为什么在__block
上没有出现ivars错误,这基本上是多余的。我不确定仅仅是因为多余的东西就应该被禁止。