- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我需要澄清dispatch_queue
与重入和死锁的关系。
在阅读此博客文章Thread Safety Basics on iOS/OS X时,我遇到了这句话:
All dispatch queues are non-reentrant, meaning you will deadlock if you attempt to dispatch_sync on the current queue.
dispatch_queue
是不可重入的,为什么在使用
dispatch_sync
调用时会出现死锁?
dispatch_sync
产生死锁。
dispatch_get_main_queue()
也将捕获主线程,并且我将陷入死锁。
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"Deadlock!!!");
});
最佳答案
All dispatch queues are non-reentrant, meaning you will deadlock if you attempt to dispatch_sync on the current queue.
那么,重入与死锁之间是什么关系呢?为什么,如果
dispatch_queue是不可重入的,当您处于
使用dispatch_sync调用?
无需阅读该文章,我可以想象该语句是引用串行队列的,因为否则它是错误的。
现在,让我们考虑一下分派(dispatch)队列如何工作的简化概念 View (以某些伪造的伪语言)。我们还假设一个串行队列,并且不考虑目标队列。
调度队列
创建调度队列时,基本上会得到一个FIFO队列,这是一个简单的数据结构,您可以在其中插入对象,然后从前移对象。
您还可以获得一些复杂的机制来管理线程池和进行同步,但是大多数机制都是为了提高性能。让我们简单地假设您还获得一个线程,该线程仅运行无限循环,即可处理队列中的消息。
void processQueue(queue) {
for (;;) {
waitUntilQueueIsNotEmptyInAThreadSaveManner(queue)
block = removeFirstObject(queue);
block();
}
}
dispatch_async
采用dispatch_async
的相同简化 View 会产生类似以下内容...
void dispatch_async(queue, block) {
appendToEndInAThreadSafeManner(queue, block);
}
它真正要做的只是获取块并将其添加到队列中。这就是为什么它立即返回的原因,它只是将块添加到数据结构的末尾。在某个时候,另一个线程会将这个块从队列中拉出并执行。
注意,这就是FIFO保证发挥作用的地方。线程拉出队列,并始终按放置在队列中的顺序执行它们。然后等待直到该块完全执行,然后再将下一个块从队列中取出。
dispatch_sync
现在,是dispatch_sync
的另一种简化 View 。在这种情况下,API保证它会等到块运行完毕才能返回。特别是,调用此函数不会违反FIFO保证。
void dispatch_sync(queue, block) {
bool done = false;
dispatch_async(queue, { block(); done = true; });
while (!done) { }
}
现在,这实际上是通过信号量完成的,因此没有cpu循环和 bool 标志,并且它不使用单独的块,但是我们试图使其保持简单。您应该知道这个主意。
将该块放置在队列中,然后函数等待,直到确定“其他线程”已将该块运行完毕为止。
再入
现在,我们可以通过多种不同的方式获得重入调用。让我们考虑最明显的。
block1 = {
dispatch_sync(queue, block2);
}
dispatch_sync(queue, block1);
这会将block1放在队列中,并等待其运行。最终,处理队列的线程将弹出block1,并开始执行它。执行block1时,会将block2放入队列中,然后等待其完成执行。
这是重新进入的意思:当您从另一个对dispatch_sync
的调用中重新输入对dispatch_sync
的调用时
重新输入dispatch_sync
导致死锁
但是,block1现在正在队列的for循环内运行。该代码正在执行block1,并且直到block1完成之前,不会再处理队列中的任何其他信息。
但是,Block1已将block2放在队列中,并等待其完成。实际上,Block2已被放置在队列中,但是它永远不会被执行。 Block1正在“等待” block2完成,但是block2坐在队列中,将其从队列中拉出并执行的代码要等到block1完成后才能运行。
无法重新输入dispatch_sync
导致死锁
现在,如果我们将代码更改为此...
block1 = {
dispatch_sync(queue, block2);
}
dispatch_async(queue, block1);
从技术上讲,我们不会重新输入dispatch_sync
。但是,我们仍然有相同的情况,只是开始于block1的线程没有等待它完成。
我们仍在运行block1,等待block2完成,但是将要运行block2的线程必须首先以block1完成。这将永远不会发生,因为处理block1的代码正在等待将block2从队列中取出并执行。
因此,调度队列的重新进入在技术上不是重新输入相同的功能,而是重新输入相同的队列处理。
根本无法重新进入队列的死锁
在最简单的情况下(也是最常见的情况),我们假设[self foo]
在主线程上被调用,这在UI回调中很常见。
-(void) foo {
dispatch_sync(dispatch_get_main_queue(), ^{
// Never gets here
});
}
这不会“重新输入”调度队列API,但具有相同的效果。我们正在主线程上运行。主线程是将块从主队列中取出并进行处理的地方。主线程当前正在执行foo
,并且在主队列上放置了一个块,然后foo
等待该块被执行。但是,只能将其从队列中取出并在主线程完成当前工作后执行。
这将永远不会发生,因为主线程在foo完成之前不会继续运行,但是直到等待运行的那个块才永远不会完成……这不会发生。
In my understanding, you can have a deadlock using dispatch_sync only if the thread you are running on is the same thread where the block is dispatch into.
如上述示例所示,情况并非如此。
此外,还有其他一些相似但不太明显的场景,尤其是当sync
访问隐藏在方法调用层中时。
避免死锁
避免死锁的唯一肯定方法是永远不要调用dispatch_sync
(虽然不完全正确,但是已经足够接近了)。如果您向用户公开队列,则尤其如此。
如果您使用独立队列,并控制其使用队列和目标队列,则可以在使用dispatch_sync
时保持一些控制。
的确,串行队列上确实有dispatch_sync
的一些有效用法,但是大多数可能是不明智的,只有当您确定您不会“同步”访问相同或另一个资源(已知后者)时,才应这样做。作为致命的拥抱)。
编辑
Jody, Thanks a lot for your answer. I really understood all of your stuff. I would like to put more points...but right now I cannot. 😢 Do you have any good tips in order to learn this under the hood stuff? – Lorenzo B.
不幸的是,我所见过的仅有的关于GCD的书并不十分先进。他们介绍了有关如何将其用于简单的通用用例的简单表面知识(我想这是大众市场书应该做的事情)。
但是,GCD是开源的。 Here is the webpage for it,包括指向其svn和git存储库的链接。但是,该网页看起来很旧(2010),我不确定代码的最新程度。对git存储库的最新提交日期为2012年8月9日。
我确定会有更多最新更新;但不确定它们会在哪里。
无论如何,我怀疑这些年来代码的概念框架已经发生了很大变化。
另外,调度队列的一般概念并不是什么新鲜事物,并且已经以很多种形式存在了很长时间。
许多个月前,我花了几天(甚至晚上)来编写内核代码(致力于我们认为是SVR4的第一个对称多处理实现),然后当我最终突破内核时,我将大部分时间都花在了编写上。 SVR4 STREAMS驱动程序(由用户空间库包装)。最终,我将其完全引入了用户空间,并构建了一些最早的HFT系统(尽管当时并没有这样称呼)。
调度队列的概念在每个方面都很普遍。它作为通用的用户空间库的出现只是最近的发展。
编辑#2
Jody, thanks for your edit. So, to recap a serial dispatch queue is not reentrant since it could produce an invalid state (a deadlock). On the contrary, an reentrant function will not produce it. Am I right? – Lorenzo B.
我猜您可以这么说,因为它不支持重入调用。
但是,我想我更愿意说死锁是防止无效状态的结果。如果发生任何其他情况,则状态将受到威胁,或者队列的定义将受到侵犯。
核心数据的performBlockAndWait
考虑-[NSManagedObjectContext performBlockAndWait]
。它是非异步的,并且 是可重入的。它在队列访问周围散布着一些小尘,因此当从“队列”中调用时,第二个块立即运行。因此,它具有我上面描述的特征。
[moc performBlock:^{
[moc performBlockAndWait:^{
// This block runs immediately, and to completion before returning
// However, `dispatch_async`/`dispatch_sync` would deadlock
}];
}];
上面的代码不会因重入而“产生死锁”(但是API不能完全避免死锁)。
但是,根据与您交谈的人的不同,执行此操作可能会产生无效(或不可预测/意外)状态。在这个简单的示例中,很清楚发生了什么,但是在更复杂的部分中,这可能更加隐蔽。
至少,对于performBlockAndWait
内的操作,您必须非常小心。
现在,实际上,这对于主队列MOC来说只是一个真正的问题,因为主运行循环正在主队列上运行,因此performBlockAndWait
会识别出该问题并立即执行该块。但是,大多数应用程序都将MOC附加到主队列,并响应主队列上的用户保存事件。
如果要查看调度队列如何与主运行循环交互,可以在主运行循环上安装CFRunLoopObserver
,并观察其如何处理主运行循环中的各种输入源。
如果您从未做到过,那将是一个有趣且具有教育意义的实验(尽管您无法假设自己观察到的总是那样)。
无论如何,我通常都尽量避免同时使用dispatch_sync
和performBlockAndWait
。
关于ios - 澄清dispatch_queue,重入和死锁,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33543263/
一、公平锁和非公平锁 1.1、公平锁和非公平锁的概述 公平锁:指多个线程按照申请锁的顺序来获取锁。 非公平锁:指在多线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取到锁
阅读目录 1、简介 2、分类 3、全局锁 4、表级锁 5、表锁 6、元数据锁
因此,在我编写的程序中,我有三个函数,为了简单起见,我们将它们称为 A、B 和 C。每个函数都需要访问资源X才能工作。 限制是A和B不允许同时运行并且必须适当同步。但是,C 可以与 A 或 B 同时运
我听说过这些与并发编程相关的词,但是锁、互斥量和信号量之间有什么区别? 最佳答案 锁只允许一个线程进入被锁定的部分,并且该锁不与任何其他进程共享。 互斥锁与锁相同,但它可以是系统范围的(由多个进程共享
这个问题已经有答案了: What is an efficient way to implement a singleton pattern in Java? [closed] (29 个回答) 已关闭
这个问题已经有答案了: What is an efficient way to implement a singleton pattern in Java? [closed] (29 个回答) 已关闭
我对标题中的主题有几个问题。首先,假设我们使用 JDBC,并且有 2 个事务 T1 和 T2。在 T1 中,我们在一个特定的行上执行 select 语句。然后我们对该行执行更新。在事务 T2 中,我们
我希望我的函数只运行一次。这意味着如果多个线程同时调用它,该函数将阻塞所有线程,只允许它运行。 最佳答案 听起来您希望存储过程进行同步。为什么不直接将同步放在应用程序本身中。 pthread_mute
if (runInDemoMode) { lock (this) { //Initalization of tables dCreator.create
我相信无论使用什么语言都可以考虑我的问题,但是为了有一些“ anchor ”,我将使用 Java 语言来描述它。 让我们考虑以下场景:我有一个扩展 Thread 的类 PickyHost 及其实例 p
我知道异步不是并行的,但我现在遇到了一个非常有趣的情况。 async function magic(){ /* some processing here */ await async () =
我们正在使用 Scala、Play 框架和 MongoDB(以 ReactiveMongo 作为我们的驱动程序)构建一个网络应用程序。应用程序架构是端到端的非阻塞。 在我们代码的某些部分,我们需要访问
我需要一个简单的锁,JavaME 超时(concurrent.lock 的反向移植需要完整的 Java 1.3)。 如果其他人已经为 JavaME 发布了经过测试的锁定代码,我宁愿使用它。 锁定是出了
根据 boost : To access the object, a weak_ptr can be converted to a shared_ptr using the shared_ptr co
关于 Mutex 和 Critical 部分之间的区别存在一个问题,但它也不处理 Locks。 所以我想知道临界区是否可以用于进程之间的线程同步。 还有信号状态和非信号状态的含义 最佳答案 在 Win
锁 最为常见的应用就是 高并发的情况下,库存的控制。本次只做简单的单机锁介绍。 直接看代码: 每请求一次库存-1. 假如库存1000,在1000个人请求之后,库存将变为0。
线程和进程 1、线程共享创建它的进程的地址空间,进程有自己的地址空间 2、线程可以访问进程所有的数据,线程可以相互访问 3、线程之间的数据是独立的 4、子进程复制线程的数据 5、子进程启动
**摘要:**细心的你也一定关注到,有的网址是https开头的,有的是http。https开头的网站前面,会有一把小锁。这是为什么呢? 本文分享自华为云社区《还不知道SSL证书已经是刚需了?赶快来了解
试图在 C 中实现一个非常简单的互斥锁(锁)我有点困惑。我知道互斥锁类似于二进制信号量,除了互斥锁还强制执行释放锁的线程的约束,必须是最近获得它的同一线程。我对如何跟踪所有权感到困惑? 这是我到目前为
在阅读了很多与上述主题相关的文章和答案之后,我仍然想知道 SQL Server 数据库引擎在以下示例中是如何工作的: 假设我们有一个名为 t3 的表: create table t3 (a int ,
我是一名优秀的程序员,十分优秀!