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

标签 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 的答案SO 问题评论说无法使用 __block 标记实例变量。答案是根据 gcc,但如果不应该这样做,为什么 Clang 会允许这样做?

最佳答案

这里重要的语义区别是,当您直接在 block 中使用 ivar 时,该 block 将对 self 进行强引用。通过创建一个引用信号量的局部变量,只有信号量被 block 捕获(通过引用),而不是 self,从而减少了保留循环的可能性。

至于 __block 限定符,您通常会使用它来指示局部变量在引用 block 中应该是可变的。但是,由于信号量变量 不会因调用signal 而发生变化,因此限定符在这里并不是绝对必要的。不过,从样式的角度来看,它仍然有一个有用的目的,因为它强调了变量的生命周期和用途。

关于为什么一个ivar可以用__block限定的话题,

why would Clang allow this if it shouldn't be done?

也许正是因为在 block 中捕获一个 ivar 意味着强烈捕获 self。无论是否使用 __block 限定符,如果您在 block 中使用 ivar,您都可能面临保留循环的风险,因此使用限定符不会产生额外的风险。最好使用局部变量(顺便说一句,它可以是对 self__weak 引用,就像 __block 限定引用一样容易到 ivar) 是明确和安全的。

关于ios - 从 Objective-C block 中修改存储在实例变量中的信号量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55244935/

相关文章:

ios - 在 ARKit 中放置、拖动和删除 SCNNode

objective-c - 为什么 NSURLConnection 在发送大量请求时会超时?

ios - 从缓存响应中停止 NSURLConnection sendAsynchronousRequest

ios - 在后台执行 AFHTTPOperation 并返回结果

ios - 垂直对齐属性在 iPad iOS 8 中不起作用

ios - 如何为我的应用程序访问 iPhone 的铃声?

iphone - 如何从通过 NSthread 调用的函数中获取返回值?

Objective-C:如何将 bool 值放入 JSON 字典中?

ios - @implementation 中的私有(private)变量

uiview - UIView 的基于 block 的动画方法的内部实现