从Objective-C块中修改存储在实例变量中的信号量

时间:2019-03-19 15:46:15

标签: ios objective-c objective-c-blocks metal

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为什么不应该这样做呢?

2 个答案:

答案 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错误,这基本上是多余的。我不确定仅仅是因为多余的东西就应该被禁止。