Изменение семафора, хранящегося в переменной экземпляра, из блока Objective-C - PullRequest
4 голосов
/ 19 марта 2019

Apple предлагает пример синхронизации процессора и графического процессора , в котором показано, как синхронизировать доступ к общим ресурсам между процессором и графическим процессором.Для этого он использует семафор, который хранится в переменной экземпляра:

@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?Действительно ли это необходимо, или подход с прямым доступом к переменной экземпляра работает так же хорошо?

В качестве примечания, ответ на этот SO вопрос отмечает, что маркировка переменных экземпляра __block не может быть сделано.Ответ в соответствии с gcc, но почему Clang допускает это, если этого не следует делать?

Ответы [ 2 ]

3 голосов
/ 19 марта 2019

Важное семантическое различие здесь заключается в том, что когда вы используете ivar непосредственно в блоке, блок получает строгую ссылку на self. Создавая локальную переменную, которая ссылается на семафор, вместо этого блок захватывает только семафор (по ссылке) вместо self, что снижает вероятность цикла сохранения.

Что касается квалификатора __block, вы обычно используете его, чтобы указать, что локальная переменная должна быть изменяемой в ссылочном блоке. Однако, поскольку семафор переменная не видоизменяется при вызове signal, спецификатор здесь не является строго необходимым. Тем не менее, он по-прежнему служит полезной цели с точки зрения стиля в том смысле, что подчеркивает время жизни и назначение переменной.

На тему, почему ивара можно квалифицировать с __block,

с чего бы Clang это разрешить, если этого не следует делать?

Возможно, именно потому, что захват ивара в блоке подразумевает сильный захват self. С или без квалификатора __block, если вы используете ивар в блоке, вы потенциально рискуете сохранить цикл, поэтому наличие классификатора там не создает дополнительного риска. Лучше использовать локальную переменную (которая, кстати, может быть __weak ссылкой на self так же легко, как __block -качественная ссылка на ивар), чтобы быть явной и безопасной.

2 голосов
/ 19 марта 2019

Я думаю, что warrenm правильно ответил на ваш вопрос о том, почему нужно использовать локальную переменную, а не ivar (и ее неявную ссылку на self).+ 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,и каждый отправленный блок будет использовать один и тот же указатель, который будет находиться в куче (то есть адрес &semaphore будет одинаковым для каждого блока).

Это не имеет смысла здесь, ИМХО, где есть только одинблок отправляется, но, надеюсь, это иллюстрирует влияние квалификатора __block на локальную переменную.(Очевидно, что традиционное использование квалификатора __block заключается в том, что вы изменяете значение рассматриваемого объекта, но здесь это не имеет значения.)

... помечая переменные экземпляра __block не может быть сделано [в gcc] ... но почему Clang разрешил это, если это не должно быть сделано?

Относительно того, почему нет ошибки на __block для ivars, как это указаноответ сказал, что это в основном избыточно.Я не уверен, что только из-за того, что что-то является излишним, это следует запретить.

...