看不到 @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 us 的 captureOutput:didOutputSampleBuffer:fromConnection:
方法中,他们只是使用 @synchronized
来确保 _recordingStatus
和 _recorder
不会在另一个线程中使用它们时在一个线程中进行更改。他们通过确保与这些变量的所有交互(无论是在一个线程中读取还是在另一个线程中更新)发生在他们自己的 @synchronized { ... }
块中来实现这一点。
但请注意 @synchronized
除了线程安全交互之外没有任何功能性目的。所有关于 "should I add a frame or not" 的逻辑都由该代码中的简单 if
语句决定。 @synchronized
指令与此无关。它们只是为了确保线程安全。
假设我有一个像下面这样的函数,每秒调用 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 us 的 captureOutput:didOutputSampleBuffer:fromConnection:
方法中,他们只是使用 @synchronized
来确保 _recordingStatus
和 _recorder
不会在另一个线程中使用它们时在一个线程中进行更改。他们通过确保与这些变量的所有交互(无论是在一个线程中读取还是在另一个线程中更新)发生在他们自己的 @synchronized { ... }
块中来实现这一点。
但请注意 @synchronized
除了线程安全交互之外没有任何功能性目的。所有关于 "should I add a frame or not" 的逻辑都由该代码中的简单 if
语句决定。 @synchronized
指令与此无关。它们只是为了确保线程安全。