假设我想使用一个 bool 状态标志来实现线程间的协作取消。 (我意识到应该最好使用 CancellationTokenSource 代替;这不是这个问题的重点。)
private volatile bool _stopping;
public void Start()
{
var thread = new Thread(() =>
{
while (!_stopping)
{
// Do computation lasting around 10 seconds.
}
});
thread.Start();
}
public void Stop()
{
_stopping = true;
}
问题:如果我在另一个线程上在 0 秒调用 Start() 并在 3 秒调用 Stop(),循环是否保证在当前迭代结束时大约 10 秒退出?
我看到的绝大多数资料表明上述内容应该按预期工作;看: MSDN ; Jon Skeet ; Brian Gideon ; Marc Gravell ; Remus Rusanu 。
但是,volatile 只在读取时生成一个获取栅栏,在写入时生成一个释放栅栏:
A volatile read has “acquire semantics”; that is, it is guaranteed to occur prior to any references to memory that occur after it in the instruction sequence. A volatile write has “release semantics”; that is, it is guaranteed to happen after any memory references prior to the write instruction in the instruction sequence. (C# Specification)
因此,不能保证 volatile 写入和 volatile 读取不会(看起来)被交换,如 Joseph Albahari 所观察到的那样。因此,后台线程可能会在当前迭代结束后继续读取 _stopping(即 false)的陈旧值。具体来说,如果我在 0 秒时调用 Start(),在 3 秒时调用 Stop(),后台任务可能不会按预期在 10 秒终止,而是在 20 秒或 30 秒终止,或者根本不会终止。
基于 acquire and release semantics ,这里有两个问题。首先,volatile 读取将被限制为从内存中刷新字段(抽象地说),不是在当前迭代结束时,而是在后续 迭代结束时,因为获取栅栏发生 < em="">after 阅读本身。其次,更关键的是,没有任何东西可以强制 volatile 写入将值提交到内存,因此根本无法保证循环会终止。
考虑以下序列流:
Time | Thread 1 | Thread 2
| |
0 | Start() called: | read value of _stopping
| | <----- acquire-fence ------------
1 | |
2 | |
3 | Stop() called: | ↑
| ------ release-fence ----------> | ↑
| set _stopping to true | ↑
4 | ↓ | ↑
5 | ↓ | ↑
6 | ↓ | ↑
7 | ↓ | ↑
8 | ↓ | ↑
9 | ↓ | ↑
10 | ↓ | read value of _stopping
| ↓ | <----- acquire-fence ------------
11 | ↓ |
12 | ↓ |
13 | ↓ | ↑
14 | ↓ | ↑
15 | ↓ | ↑
16 | ↓ | ↑
17 | ↓ | ↑
18 | ↓ | ↑
19 | ↓ | ↑
20 | | read value of _stopping
| | <----- acquire-fence ------------
最重要的部分是内存栅栏,标记为 --> 和 <-- ,代表线程同步点。 _stopping 的 volatile 读取最多只能(似乎)向上移动到其线程的先前获取栅栏。但是,volatile 写入可以(似乎)无限期地向下移动,因为在它的线程上没有其他释放栅栏跟随它。换句话说,在对 _stopping 的写入和它的任何读取之间没有“synchronizes-with”(“先于发生”,“可见”)关系。
附言我知道 MSDN 对 volatile 关键字提供了非常有力的保证。但是,专家一致认为 MSDN 是错误的(并且没有得到 ECMA 规范的支持):
The MSDN documentation states that use of the volatile keyword “ensures that the most up-to-date value is present in the field at all times”. This is incorrect, since as we’ve seen [in the previous example], a write followed by a read can be reordered. (Joseph Albahari)
最佳答案
If I call
Start()at 0s andStop()at 3s on another thread, is the loop guaranteed to exit at the end of the current iteration at around 10s?
是的,7 秒绝对足以让一个线程感知到 _stopping 变量的变化。
对于提供任何类型的可见性障碍(内存顺序)的每个变量,任何语言的规范都应提供以下保证:
Any change of the variable (with special memory order) from one thread will be observed in other threads during finit and bounded period of time.
没有这个保证,即使是变量的内存顺序特征也是无用的。
C# 规范明确提供了关于volatile 变量的保证,但我找不到相应的文本。
请注意,这种关于有限时间的保证与内存顺序保证(“获取”、“释放”等)无关,并且不能从障碍的定义中推断 和内存顺序。
什么时候说
I call
Stop()at 3s
一个暗示,有一些可见的效果(例如,信息打印到终端),这允许他声称大约 3 秒的时间戳(因为打印语句已在之后发出em> Stop()).
随着 C# 规范的优雅发挥(“10.10 执行顺序”):
Execution shall proceed such that the side effects of each executing thread are preserved at critical execution points. A side effect is defined as a read or write of a volatile field, a write to a non-volatile variable, a write to an external resource, and the throwing of an exception. The critical execution points at which the order of these side effects shall be preserved are references to volatile fields (§17.4.3), lock statements (§15.12), and thread creation and termination.
假设打印是一个关键执行点(可能它使用了锁),您可能有信心目前将_stopping volatile 变量赋值作为端effect 对检查给定变量的另一个线程可见。
虽然允许编译器在代码中向前移动volatile变量的赋值,但它不能无限期地这样做:
不能在函数调用之后移动赋值,因为编译器不能对函数体做任何假设。
如果在一个循环内进行赋值,则应在下一个循环的另一个赋值之前完成。
虽然可以想象具有 1000 个连续简单赋值(对其他变量)的代码,因此 volatile 赋值可以延迟 1000 条指令,但编译器确实会执行此类延迟。即使是这样,在现代 CPU 上执行 1000 条简单指令也不会超过几微秒。
从 CPU 的角度来看,情况更简单:没有 CPU 会延迟对内存单元的分配超过有限数量的指令。
总的来说,volatile 变量的赋值只能在非常有限的指令数上延迟。
关于c# - 流行的 "volatile polled flag"模式坏了吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44121866/
我正在尝试测试是否存在表单。我是Rails新手。我的new.html.erb_spec.rb文件的内容是:require'spec_helper'describe"messages/new.html.erb"doit"shouldrendertheform"dorender'/messages/new.html.erb'reponse.shouldhave_form_putting_to(@message)with_submit_buttonendendView本身,new.html.erb,有代码:当我运行rspec时,它失败了:1)messages/new.html.erbshou
我在从html页面生成PDF时遇到问题。我正在使用PDFkit。在安装它的过程中,我注意到我需要wkhtmltopdf。所以我也安装了它。我做了PDFkit的文档所说的一切......现在我在尝试加载PDF时遇到了这个错误。这里是错误:commandfailed:"/usr/local/bin/wkhtmltopdf""--margin-right""0.75in""--page-size""Letter""--margin-top""0.75in""--margin-bottom""0.75in""--encoding""UTF-8""--margin-left""0.75in""-
我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co
我主要使用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
为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar
鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende
我遵循MichaelHartl的“RubyonRails教程:学习Web开发”,并创建了检查用户名和电子邮件长度有效性的测试(名称最多50个字符,电子邮件最多255个字符)。test/helpers/application_helper_test.rb的内容是:require'test_helper'classApplicationHelperTest在运行bundleexecraketest时,所有测试都通过了,但我看到以下消息在最后被标记为错误:ERROR["test_full_title_helper",ApplicationHelperTest,1.820016791]test
我正在尝试从Postgresql表(table1)中获取数据,该表由另一个相关表(property)的字段(table2)过滤。在纯SQL中,我会这样编写查询:SELECT*FROMtable1JOINtable2USING(table2_id)WHEREtable2.propertyLIKE'query%'这工作正常:scope:my_scope,->(query){includes(:table2).where("table2.property":query)}但我真正需要的是使用LIKE运算符进行过滤,而不是严格相等。然而,这是行不通的:scope:my_scope,->(que
我正在尝试编写一个将文件上传到AWS并公开该文件的Ruby脚本。我做了以下事情:s3=Aws::S3::Resource.new(credentials:Aws::Credentials.new(KEY,SECRET),region:'us-west-2')obj=s3.bucket('stg-db').object('key')obj.upload_file(filename)这似乎工作正常,除了该文件不是公开可用的,而且我无法获得它的公共(public)URL。但是当我登录到S3时,我可以正常查看我的文件。为了使其公开可用,我将最后一行更改为obj.upload_file(file
当我尝试安装Ruby时遇到此错误。我试过查看this和this但无济于事➜~brewinstallrubyWarning:YouareusingOSX10.12.Wedonotprovidesupportforthispre-releaseversion.Youmayencounterbuildfailuresorotherbreakages.Pleasecreatepull-requestsinsteadoffilingissues.==>Installingdependenciesforruby:readline,libyaml,makedepend==>Installingrub