草庐IT

c++ - std::promise set_value 和线程安全

coder 2023-06-01 原文

我对 std::promise::set_value() 上的线程安全要求有点困惑。

standard says :

Effects: Atomically stores the value r in the shared state and makes that state ready

但是,它也说 promise::set_value() 只能用于设置一次值。如果多次调用,则会抛出 std::future_error。所以你只能设置一次 Promise 的值。

事实上,std::promise 的几乎每个教程、在线代码示例或实际用例都涉及 2 个线程之间的通信 channel ,其中一个线程调用std::future::get(),其他线程调用std::promise::set_value()

我从未见过多个线程可能调用 std::promise::set_value() 的用例,即使调用了,除了一个线程之外,其他所有线程都会导致 std: :future_error 要抛出的异常。

那么为什么调用 std::promise::set_value() 的标准要求是原子的?从多个线程同时调用 std::promise::set_value() 的用例是什么?


编辑:

由于此处投票最多的答案并未真正回答我的问题,因此我认为我的问题尚不清楚。所以,澄清一下:我知道 future 和 promise 的用途以及它们是如何运作的。我的问题是,为什么标准坚持 std::promise::set_value() 必须是原子的?这是一个比“为什么在对 promise::set_value() 的调用和对 future::get() 的调用之间不能存在竞争”的更微妙的问题?

事实上,这里的许多答案(错误地)都回应说原因是因为如果 std::promise::set_value() 不是原子的,那么 std::future::get() 可能会导致竞争条件。但是这是错误的。

避免竞争条件的唯一要求是 std::promise::set_value() 必须有 happens-beforestd::future::get() 的关系——换句话说,必须保证当 std::future::wait() 返回时, std::promise::set_value() 已完成。

这与 std::promise::set_value() 本身是否是原子的完全正交。在使用条件变量的典型实现中,std::future::get()/wait() 将等待条件变量。然后,std::promise::set_value() 可以非原子地 执行任意复杂的计算来设置实际值。然后它会通知共享条件变量,(暗示具有释放语义的内存栅栏),std::future::get() 会唤醒并安全读取结果。

因此,std::promise::set_value() 本身不需要是原子的来避免这里的竞争条件 - 它只需要满足 happens-beforestd::future::get()的关系。

所以我的问题是:为什么 C++ 标准坚持 std::promise::set_value() 实际上必须是一个原子操作,就好像一个对 std::promise::set_value() 的调用完全在互斥锁下执行?我认为没有理由存在这个要求,除非多个线程同时调用 std::promise::set_value() 有某种原因或用例。而且我想不出这样的用例,因此提出了这个问题。

最佳答案

如果不是原子存储,那么两个线程可以同时调用 promise::set_value,它执行以下操作:

  1. 检查 future 是否准备就绪(即有存储值或异常)
  2. 存储值
    • 标记状态就绪
    • 释放任何阻碍共享状态变为就绪的内容

通过使这个序列原子化,第一个执行的线程 (1) 一直到 (3),任何其他同时调用 promise::set_value 的线程都将失败(1) 并使用 promise_already_satisfied 引发 future_error

如果没有原子性,两个线程可能会存储它们的值,然后一个会成功标记状态就绪,另一个会引发异常,即相同的结果,除了它可能是来自看到异常的线程的值。

在许多情况下,哪个线程“获胜”可能并不重要,但当它确实重要时,如果没有原子性保证,您将需要在 promise::set_value 调用周围包装另一个互斥锁。比较和交换等其他方法不起作用,因为您无法检查 future (除非它是 shared_future)来查看您的值(value)是否赢了。

当哪个线程“获胜”无关紧要时,您可以给每个线程自己的 future ,并使用 std::experimental::when_any收集碰巧可用的第一个结果。


经过一些历史研究后编辑:

虽然上述(两个线程使用同一个 promise 对象)看起来不是一个好的用例,但它肯定是由当代论文之一将 future 引入 C++ : N2744 .本文提出了几个用例,它们有这样的冲突线程调用 set_value,我将在这里引用它们:

Second, consider use cases where two or more asynchronous operations are performed in parallel and "compete" to satisfy the promise. Some examples include:

  • A sequence of network operations (e.g. request a web page) is performed in conjunction with a wait on a timer.
  • A value may be retrieved from multiple servers. For redundancy, all servers are tried but only the first value obtained is needed.

In both examples, the first asynchronous operation to complete is the one that satisfies the promise. Since either operation may complete second, the code for both must be written to expect that calls to set_value() may fail.

关于c++ - std::promise set_value 和线程安全,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45626919/

有关c++ - std::promise set_value 和线程安全的更多相关文章

  1. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  2. ruby - 如何使用 Ruby aws/s3 Gem 生成安全 URL 以从 s3 下载文件 - 2

    我正在编写一个小脚本来定位aws存储桶中的特定文件,并创建一个临时验证的url以发送给同事。(理想情况下,这将创建类似于在控制台上右键单击存储桶中的文件并复制链接地址的结果)。我研究过回形针,它似乎不符合这个标准,但我可能只是不知道它的全部功能。我尝试了以下方法:defauthenticated_url(file_name,bucket)AWS::S3::S3Object.url_for(file_name,bucket,:secure=>true,:expires=>20*60)end产生这种类型的结果:...-1.amazonaws.com/file_path/file.zip.A

  3. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

    我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

  4. ruby - 如何安全地删除文件? - 2

    在Ruby中是否有Gem或安全删除文件的方法?我想避免系统上可能不存在的外部程序。“安全删除”指的是覆盖文件内容。 最佳答案 如果您使用的是*nix,一个很好的方法是使用exec/open3/open4调用shred:`shred-fxuz#{filename}`http://www.gnu.org/s/coreutils/manual/html_node/shred-invocation.html检查这个类似的帖子:Writingafileshredderinpythonorruby?

  5. ruby - 使用 `+=` 和 `send` 方法 - 2

    如何将send与+=一起使用?a=20;a.send"+=",10undefinedmethod`+='for20:Fixnuma=20;a+=10=>30 最佳答案 恐怕你不能。+=不是方法,而是语法糖。参见http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_expressions.html它说Incommonwithmanyotherlanguages,Rubyhasasyntacticshortcut:a=a+2maybewrittenasa+=2.你能做的最好的事情是:

  6. ruby-on-rails - Rails 单表继承 : How to override the value written to the type field - 2

    在我的系统中,我已经定义了STI。Dog继承自Animal,在animals表中有一个type列,其值为"Dog"。现在我想让SpecialDog继承自dog,只是为了在某些特殊情况下稍微修改一下行为。数据还是一样。我需要通过SpecialDog运行的所有查询,以返回数据库中类型为Dog的值。我的问题是因为我有一个type列,rails将WHERE"animals"."type"IN('SpecialDog')附加到我的查询中,所以我不能获取原始的Dog条目。所以我想要的是以某种方式覆盖rails在通过SpecialDog访问数据库时使用的值,使其表现得像Dog。有没有办法覆盖用于类型

  7. ruby - 如何让Ruby捕获线程中的语法错误 - 2

    我正在尝试使用ruby​​编写一个双线程客户端,一个线程从套接字读取数据并将其打印出来,另一个线程读取本地数据并将其发送到远程服务器。我发现的问题是Ruby似乎无法捕获线程内的错误,这是一个示例:#!/usr/bin/rubyThread.new{loop{$stdout.puts"hi"abc.putsefsleep1}}loop{sleep1}显然,如果我在线程外键入abc.putsef,代码将永远不会运行,因为Ruby将报告“undefinedvariableabc”。但是,如果它在一个线程内,则没有错误报告。我的问题是,如何让Ruby捕获这样的错误?或者至少,报告线程中的错误?

  8. ruby - 用 YAML.load 解析 json 安全吗? - 2

    我正在使用ruby2.1.0我有一个json文件。例如:test.json{"item":[{"apple":1},{"banana":2}]}用YAML.load加载这个文件安全吗?YAML.load(File.read('test.json'))我正在尝试加载一个json或yaml格式的文件。 最佳答案 YAML可以加载JSONYAML.load('{"something":"test","other":4}')=>{"something"=>"test","other"=>4}JSON将无法加载YAML。JSON.load("

  9. ruby - 如何在 ruby​​ 中运行后台线程? - 2

    我是ruby​​的新手,我认为重新构建一个我用C#编写的简单聊天程序是个好主意。我正在使用Ruby2.0.0MRI(Matz的Ruby实现)。问题是我想在服务器运行时为简单的服务器命令提供I/O。这是从示例中获取的服务器。我添加了使用gets()获取输入的命令方法。我希望此方法在后台作为线程运行,但该线程正在阻塞另一个线程。require'socket'#Getsocketsfromstdlibserver=TCPServer.open(2000)#Sockettolistenonport2000defcommandsx=1whilex==1exitProgram=gets.chomp

  10. ruby - 如何计算 Liquid 中的变量 +1 - 2

    我对如何计算通过{%assignvar=0%}赋值的变量加一完全感到困惑。这应该是最简单的任务。到目前为止,这是我尝试过的:{%assignamount=0%}{%forvariantinproduct.variants%}{%assignamount=amount+1%}{%endfor%}Amount:{{amount}}结果总是0。也许我忽略了一些明显的东西。也许有更好的方法。我想要存档的只是获取运行的迭代次数。 最佳答案 因为{{incrementamount}}将输出您的变量值并且不会影响{%assign%}定义的变量,我

随机推荐