草庐IT

c++ - 为什么不重新锁定互斥锁的 condition_variable 没有等待函数

coder 2024-01-31 原文

考虑以下示例。

std::mutex mtx;
std::condition_variable cv;

void f()
{
  {
    std::unique_lock<std::mutex>  lock( mtx );
    cv.wait( lock );  // 1
  }
  std::cout << "f()\n";
}

void g()
{
  std::this_thread::sleep_for( 1s );
  cv.notify_one();
}

int main()
{
  std::thread  t1{ f };
  std::thread  t2{ g };
  t2.join();
  t1.join();
}
g() “知道”f()正在等待我想讨论的场景。
根据 cppreference.com不需要g()在调用之前锁定互斥锁 notify_one .现在在标记为“1”的行中 cv将释放互斥锁并在发送通知后重新锁定它。 lock 的析构函数之后立即再次释放它。这似乎是多余的,特别是因为锁定是昂贵的。 (我知道在某些情况下需要锁定互斥锁。但这里不是这种情况。)

为什么condition_variable没有函数“wait_nolock ”一旦通知到达就不会重新锁定互斥锁。如果答案是 pthreads 不提供这样的功能:为什么 pthreads 不能扩展以提供它?是否有实现所需行为的替代方法?

最佳答案

你误解了你的代码是做什么的。

您的在线代码// 1完全不阻塞是自由的。 condition_variables可以(并且将会!)有虚假的唤醒——他们可以毫无理由地唤醒。

您负责检查唤醒是否是虚假的。

使用 condition_variable正确地需要三件事:

  • 一个 condition_variable
  • 一个 mutex
  • mutex 保护的一些数据

  • 由互斥锁保护的数据被修改(在 mutex 下)。然后(mutex 可能脱离),condition_variable被通知。

    在另一端,您锁定了 mutex ,然后等待条件变量。当您醒来时,您的 mutex被重新锁定,您可以通过查看 mutex 保护的数据来测试唤醒是否是虚假的。 .如果是有效唤醒,则处理并继续。

    如果它不是有效的唤醒,您将返回等待。

    在您的情况下,您没有保护任何数据,无法区分虚假唤醒和真实唤醒,并且您的设计不完整。

    毫不奇怪,由于设计不完整,您看不到 mutex 的原因。被重新锁定:它被重新锁定,因此您可以安全地检查数据以查看唤醒是否是虚假的。

    如果您想知道为什么这样设计条件变量,可能是因为这种设计比“可靠”的设计更有效(无论出于何种原因),并且 C++ 并没有公开更高级别的原语,而是公开了更低级别的更高效的原语。

    在此基础上构建更高级别的抽象并不难,但需要进行设计决策。这是一个建立在 std::experimental::optional 之上的:
    template<class T>
    struct data_passer {
      std::experimental::optional<T> data;
      bool abort_flag = false;
      std::mutex guard;
      std::condition_variable signal;
    
      void send( T t ) {
        {
          std::unique_lock<std::mutex> _(guard);
          data = std::move(t);
        }
        signal.notify_one();
      }
      void abort() {
        {
          std::unique_lock<std::mutex> _(guard);
          abort_flag = true;
        }
        signal.notify_all();
      }        
      std::experimental::optional<T> get() {
        std::unique_lock<std::mutex> _(guard);
        signal.wait( _, [this]()->bool{
          return data || abort_flag;
        });
        if (abort_flag) return {};
        T retval = std::move(*data);
        data = {};
        return retval;
      }
    };
    

    现在,每个send可能导致 get在另一端取得成功。如果不止一个 send发生时,只有最新的一个被 get 消耗。 .如果以及何时 abort_flag设置,而不是 get()立即返回 {} ;

    以上支持多个消费者和生产者。

    如何使用上述内容的一个示例是预览状态源(例如 UI 线程)和一个或多个预览渲染器(它们的速度不够快,无法在 UI 线程中运行)。

    预览状态将预览状态转储到 data_passer<preview_state>随意。渲染器竞争,其中之一抢占了它。然后他们渲染它,并将它传回(通过任何机制)。

    如果预览状态的速度比渲染器消耗它们的速度快,则只有最近的状态是感兴趣的,因此较早的状态将被丢弃。但是现有的预览不会因为出现新状态而中止。

    下面询问有关竞争条件的问题。

    如果正在通信的数据是 atomic ,我们不能没有“发送”端的互斥锁吗?

    所以像这样:
    template<class T>
    struct data_passer {
      std::atomic<std::experimental::optional<T>> data;
      std::atomic<bool> abort_flag = false;
      std::mutex guard;
      std::condition_variable signal;
    
      void send( T t ) {
        data = std::move(t); // 1a
        signal.notify_one(); // 1b
      }
      void abort() {
        abort_flag = true;   // 1a
        signal.notify_all(); // 1b
      }        
      std::experimental::optional<T> get() {
        std::unique_lock<std::mutex> _(guard); // 2a
        signal.wait( _, [this]()->bool{ // 2b
          return data.load() || abort_flag.load(); // 2c
        });
        if (abort_flag.load()) return {};
        T retval = std::move(*data.load());
        // data = std::experimental::nullopt;  // doesn't make sense
        return retval;
      }
    };
    

    以上无法正常工作。

    我们从监听线程开始。它执行步骤 2a,然后等待 (2b)。它在步骤 2c 评估条件,但尚未从 lambda 返回。

    广播线程然后执行步骤 1a(设置数据),然后通知条件变量。此时,没有人在等待条件变量(lambda 中的代码不算数!)。

    监听线程然后完成 lambda,并返回“虚假唤醒”。然后它阻塞在条件变量上,并且永远不会注意到数据已发送。
    std::mutex在等待条件变量时使用必须保护对条件变量“传递”的数据的写入(无论您做什么测试以确定唤醒是否是虚假的),以及读取(在 lambda 中),或“丢失”的可能性信号”存在。 (至少在一个简单的实现中:更复杂的实现可以为“常见情况”创建无锁路径,并且只在双重检查中使用 mutex。这超出了这个问题的范围。)

    使用 atomic variables 并没有解决这个问题,因为“确定消息是否是虚假的”和“在条件变量中重新等待”这两个操作对于消息的“虚假性”必须是原子的。

    关于c++ - 为什么不重新锁定互斥锁的 condition_variable 没有等待函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32978066/

    有关c++ - 为什么不重新锁定互斥锁的 condition_variable 没有等待函数的更多相关文章

    1. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

      类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

    2. ruby - 难道Lua没有和Ruby的method_missing相媲美的东西吗? - 2

      我好像记得Lua有类似Ruby的method_missing的东西。还是我记错了? 最佳答案 表的metatable的__index和__newindex可以用于与Ruby的method_missing相同的效果。 关于ruby-难道Lua没有和Ruby的method_missing相媲美的东西吗?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/7732154/

    3. ruby - 为什么 4.1%2 使用 Ruby 返回 0.0999999999999996?但是 4.2%2==0.2 - 2

      为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返

    4. ruby-on-rails - rails 目前在重启后没有安装 - 2

      我有一个奇怪的问题:我在rvm上安装了ruby​​onrails。一切正常,我可以创建项目。但是在我输入“railsnew”时重新启动后,我有“程序'rails'当前未安装。”。SystemUbuntu12.04ruby-v"1.9.3p194"gemlistactionmailer(3.2.5)actionpack(3.2.5)activemodel(3.2.5)activerecord(3.2.5)activeresource(3.2.5)activesupport(3.2.5)arel(3.0.2)builder(3.0.0)bundler(1.1.4)coffee-rails(

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

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

    6. ruby - 如何在续集中重新加载表模式? - 2

      鉴于我有以下迁移: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

    7. ruby-on-rails - 如何使用 instance_variable_set 正确设置实例变量? - 2

      我正在查看instance_variable_set的文档并看到给出的示例代码是这样做的:obj.instance_variable_set(:@instnc_var,"valuefortheinstancevariable")然后允许您在类的任何实例方法中以@instnc_var的形式访问该变量。我想知道为什么在@instnc_var之前需要一个冒号:。冒号有什么作用? 最佳答案 我的第一直觉是告诉你不要使用instance_variable_set除非你真的知道你用它做什么。它本质上是一种元编程工具或绕过实例变量可见性的黑客攻击

    8. ruby-on-rails - active_admin 目录中的常量警告重新声明 - 2

      我正在使用active_admin,我在Rails3应用程序的应用程序中有一个目录管理,其中包含模型和页面的声明。时不时地我也有一个类,当那个类有一个常量时,就像这样:classFooBAR="bar"end然后,我在每个必须在我的Rails应用程序中重新加载一些代码的请求中收到此警告:/Users/pupeno/helloworld/app/admin/billing.rb:12:warning:alreadyinitializedconstantBAR知道发生了什么以及如何避免这些警告吗? 最佳答案 在纯Ruby中:classA

    9. ruby - 在没有 sass 引擎的情况下使用 sass 颜色函数 - 2

      我想在一个没有Sass引擎的类中使用Sass颜色函数。我已经在项目中使用了sassgem,所以我认为搭载会像以下一样简单:classRectangleincludeSass::Script::FunctionsdefcolorSass::Script::Color.new([0x82,0x39,0x06])enddefrender#hamlengineexecutedwithcontextofself#sothatwithintemlateicouldcall#%stop{offset:'0%',stop:{color:lighten(color)}}endend更新:参见上面的#re

    10. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

      如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

    随机推荐