看不到 @synchronized 如何与异步块一起工作

Failing to see how @synchronized works with asynchronous blocks

假设我有一个像下面这样的函数,每秒调用 30 次(此代码是我试图理解的 Apple 创建的更复杂代码的简化。Apple 原始代码的想法是这:就像我说的那样,doSomethingWithImage 每秒调用 30 次。所以,整个方法有 1/30 秒的时间来做所有事情,但想象一下 doSomethingThatWillTakeTime 比预期花费更多,而 doSomethingWithImage 是再次调用,而 doSomethingThatWillTakeTime 已经在执行。如果是这种情况,代码必须删除该图像并且什么也不做。

// code...
@synchronized (self);
  [self doSomethingWithImage:image];
}

// ...

// call under @synchronized( self )
- (void)doSomethingWithImage:(UIImage *)image
{
    self.myImage = image;

    [self executeBlockAsync:^{ 

        UIImage *myImage = nil;

        @synchronized( self ) 
        {
            myImage = self.myImage; 
            if (myImage) { 
                self.myImage = nil; // drop the image
            }
        }

        if ( myImage ) { 
            [self doSomethingThatWillTakeTime:myImage];  
        }
    }];
}


 (void)executeBlockAsync:(dispatch_block_t)block
{
    dispatch_async( _myQueue, ^{
            callbackBlock();
    } );
}

我的问题是看这段代码我看不出这是怎么做到的。对于以何种顺序执行哪些行,我没有心理印象。可能我不明白 @synchronized 在这样做时的作用,究竟是什么被锁定或没有被锁定。

关键是executeBlockAsync,它又调用了dispatch_async。所以真正的工作被分派到一个后台队列,它很可能被配置为顺序工作。 doSomethingWithImage 立即 returns 并且永远不会花费 1/30 秒。

所以调度保证图像处理是串行完成的。只要图像处理平均不超过 1/30 秒,此方法就有效。单个图像可能需要更长的时间。如果持续时间较长,队列将填满。

我不明白 @synchronized 在这段代码中做了什么。仅当有更多代码(此处未显示)使用相同的 @synchronized 块时才有意义。

不是代码"drops the image and does nothing"。使用术语 "drop" 可能有点误导;发生的情况是,如果有新图像,尚未传送给代表的图像将被覆盖。

发生的事情是这样的:

  • 图像 1 存储在 self.myImage
  • 异步任务在串行调度队列中排队以处理图像 1。
  • 执行此任务时,它会获取当前 self.myImage,然后清除 self.myImage
  • 如果拾取了非零图像,则将其传递给doSomethingThatWillTakeTime

稍后,

  • 图像 2 存储在 self.myImage
  • 异步任务在串行调度队列中排队以处理图像 2
  • 由于是串行调度队列,如果第一个任务没有完成,这个任务就会等待。

现在说,

  • 图片 3 存储在 self.myImage - 请注意,这不会检查 self.myImage 是否为零,它只是覆盖它,所以如果图片 2 从未被处理过,它已经 "dropped"
  • 异步任务在串行调度队列中排队以处理图像 3,但它也必须等待
  • 现在,当第一个任务完成时,第二个任务将执行。它将从 self.myImage(现在是图像 3)中获取值并清除 self.myImage
  • 图 3 传递给 doSomethingThatWillTakeTime
  • 第二个任务完成后,第三个任务将开始。
  • 它将从 self.myImage 中得到 nil 什么都不做。

所以你可以看到图像 2 是如何被丢弃的,因为它是陈旧的;图 3 已经到了,所以没有必要处理图 2。

本质上,这段代码实现了一个大小为 1

circular buffer

@synchronized唯一做的就是确保"thread safe",与一些对象或变量的同步交互。具体来说,它确保您不会从一个线程访问这些资源,而它们可能在其他地方被另一个线程改变。如果一个线程在 @synchronized(self) {...} 块内并且另一个线程遇到它自己的 @synchronized(self) {...} 块,则后一个代码将不会启动 运行 直到另一个线程完成它的 @synchronized 块.

这只是 Threading Programming Guide: Synchronization. (Or you can also use dedicated GCD queues, either serial queues or concurrent queues that use the reader-writer pattern, to manage this synchronization as outlined in Concurrency Programming Guide: Migrating Away from Threads: Eliminating Lock-based Code 中概述的众多同步技术之一。但那完全是一个单独的话题。)

在您 shared with uscaptureOutput:didOutputSampleBuffer:fromConnection: 方法中,他们只是使用 @synchronized 来确保 _recordingStatus_recorder 不会在另一个线程中使用它们时在一个线程中进行更改。他们通过确保与这些变量的所有交互(无论是在一个线程中读取还是在另一个线程中更新)发生在他们自己的 @synchronized { ... } 块中来实现这一点。

但请注意 @synchronized 除了线程安全交互之外没有任何功能性目的。所有关于 "should I add a frame or not" 的逻辑都由该代码中的简单 if 语句决定。 @synchronized 指令与此无关。它们只是为了确保线程安全。