由于不必要的性能影响,我的问题特别提到了为什么要这样设计。
当线程 T1 有这个代码时:
cv.acquire()
cv.wait()
cv.release()
线程 T2 有这个代码:
cv.acquire()
cv.notify() # requires that lock be held
cv.release()
发生的情况是 T1 等待并释放锁,然后 T2 获取它,通知 cv 唤醒 T1。现在,在 T2 的释放和 T1 从 wait() 返回后重新获取之间存在竞争条件。如果 T1 先尝试重新获取,它将不必要地重新挂起,直到 T2 的 release() 完成。
注意:我故意不使用 with 语句,以便通过显式调用更好地说明比赛。
这似乎是一个设计缺陷。是否有任何已知的理由,或者我错过了什么?
最佳答案
这不是一个确定的答案,但它应该涵盖我设法收集的有关此问题的相关详细信息。
首先,Python 的 threading implementation is based on Java's . Java 的 Condition.signal() 文档内容如下:
An implementation may (and typically does) require that the current thread hold the lock associated with this Condition when this method is called.
现在,问题是为什么在 Python 中强制这种行为。但首先我想介绍每种方法的优缺点。
至于为什么有些人认为持有锁通常更好,我发现了两个主要论点:
从服务员 acquire() 获得锁的那一刻起——也就是说,在 wait() 上释放它之前——保证会收到通知信号。如果相应的 release() 发生在发信号之前,这将允许序列(其中 P=Producer 和 C=Consumer) P:释放(); C:获取(); P:通知(); C:wait() 这种情况下,同一流程的acquire()对应的wait()会漏掉信号。在某些情况下这并不重要(甚至可以被认为更准确),但在某些情况下这是不可取的。这是一个论点。
当你在锁之外notify(),这可能会导致调度优先级倒置;也就是说,低优先级的线程可能最终会优先于高优先级的线程。考虑一个有一个生产者和两个消费者(LC=低优先级消费者和HC=高优先级消费者)的工作队列,其中LC是当前正在执行一个工作项,并且 HC 在 wait() 中被阻止。
可能会出现以下顺序:
P LC HC
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
execute(item) (in wait())
lock()
wq.push(item)
release()
acquire()
item = wq.pop()
release();
notify()
(wake-up)
while (wq.empty())
wait();
而如果 notify() 发生在 release() 之前,LC 将无法 acquire() 在 HC 被唤醒之前。这是发生优先级反转的地方。这是第二个论点。
支持在锁外通知的论点是高性能线程,其中线程不需要回到 sleep 状态,只是为了在它获得的下一个时间片再次唤醒——这已经解释了它是如何实现的可能发生在我的问题中。
threading 模块正如我所说,在 Python 中,您必须在通知时持有锁。具有讽刺意味的是,内部实现不允许底层操作系统避免优先级反转,因为它对服务员强制执行 FIFO 顺序。当然,服务员的顺序是确定性的这一事实可能会派上用场,但问题仍然是,当有人认为区分锁和条件变量会更精确时,为什么要执行这样的事情,因为在一些需要优化并发和最小阻塞的流,acquire() 不应该自己注册前面的等待状态,而只有 wait() 调用自己。
可以说,Python 程序员无论如何都不会关心性能到这种程度——尽管这仍然不能回答为什么在实现标准库时,不应允许多种标准行为成为可能。
还有一点要说的是,threading 模块的开发人员可能出于某种原因特别想要一个 FIFO 顺序,并发现这是实现它的最佳方式,并且希望以牺牲其他(可能更普遍的)方法为代价将其确定为 Condition。为此,他们应该得到怀疑的好处,直到他们自己可以解释。
关于python - 为什么 Python threading.Condition() notify() 需要锁?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46076186/
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
当我使用Bundler时,是否需要在我的Gemfile中将其列为依赖项?毕竟,我的代码中有些地方需要它。例如,当我进行Bundler设置时:require"bundler/setup" 最佳答案 没有。您可以尝试,但首先您必须用鞋带将自己抬离地面。 关于ruby-我需要将Bundler本身添加到Gemfile中吗?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/4758609/
我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co
我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返
我注意到像bundler这样的项目在每个specfile中执行requirespec_helper我还注意到rspec使用选项--require,它允许您在引导rspec时要求一个文件。您还可以将其添加到.rspec文件中,因此只要您运行不带参数的rspec就会添加它。使用上述方法有什么缺点可以解释为什么像bundler这样的项目选择在每个规范文件中都需要spec_helper吗? 最佳答案 我不在Bundler上工作,所以我不能直接谈论他们的做法。并非所有项目都checkin.rspec文件。原因是这个文件,通常按照当前的惯例,只
它不等于主线程的binding,这个toplevel作用域是什么?此作用域与主线程中的binding有何不同?>ruby-e'putsTOPLEVEL_BINDING===binding'false 最佳答案 事实是,TOPLEVEL_BINDING始终引用Binding的预定义全局实例,而Kernel#binding创建的新实例>Binding每次封装当前执行上下文。在顶层,它们都包含相同的绑定(bind),但它们不是同一个对象,您无法使用==或===测试它们的绑定(bind)相等性。putsTOPLEVEL_BINDINGput
我可以得到Infinity和NaNn=9.0/0#=>Infinityn.class#=>Floatm=0/0.0#=>NaNm.class#=>Float但是当我想直接访问Infinity或NaN时:Infinity#=>uninitializedconstantInfinity(NameError)NaN#=>uninitializedconstantNaN(NameError)什么是Infinity和NaN?它们是对象、关键字还是其他东西? 最佳答案 您看到打印为Infinity和NaN的只是Float类的两个特殊实例的字符串