【问题标题】:std::lock_guard causing undefined behaviorstd::lock_guard 导致未定义的行为
【发布时间】:2016-04-09 08:06:54
【问题描述】:

编辑: 看起来,问题在于我实际上并没有创建 lock_guard 的本地实例,而只是一个匿名的临时实例,它立即再次被销毁,如下面的 cmets 所指出的。

Edit2:启用 clang 的线程清理器有助于在运行时查明此类问题。它可以通过

clang++ -std=c++14 -stdlib=libc++ -fsanitize=thread *.cpp -pthread

这可能在某种程度上是一个重复的问题,但我找不到任何东西,所以如果它确实是重复的,我很抱歉。无论如何,这应该是一个初学者的问题。

我在玩一个简单的“计数器”类,比如在文件中内联

Counter.hpp:

#ifndef CLASS_COUNTER_HPP_
#define CLASS_COUNTER_HPP_

#include <mutex>
#include <string>
#include <exception>

class Counter
{
     public:
          explicit Counter(std::size_t v = 0) : value_{v} {}

          std::size_t value() const noexcept { return value_; }

//          void increment() { ++value_; }        // not an atomic operation : ++value_ equals value_ = value_ + 1
                                                // --> 3 operations: read, add, assign
          void increment() noexcept
          {
               mutex_.lock();
               ++value_;
               mutex_.unlock();
          }

//          void decrement() noexcept
//          {
//               mutex_.lock();
//               --value_;                      // possible underflow
//               mutex_.unlock();
//          }

          void decrement()
          {
               std::lock_guard<std::mutex>{mutex_};
               if (value_ == 0)
               {
                    std::string message{"New Value ("+std::to_string(value_-1)+") too low, must be at least 0"};
                    throw std::logic_error{message};
               }
               --value_;
          }

     private:
          std::size_t value_;
          std::mutex mutex_;
};

#endif

在 main.cpp 中,一个 Counter 实例应该是递增和递减的 同时:

main.cpp:

#include <iostream>
#include <iomanip>
#include <array>
#include <thread>
#include <exception>

#include "Counter.hpp"

     int
main ()
{
     Counter counter{};
     std::array<std::thread,4> threads;
     auto operation = [&counter]()
     {
          for (std::size_t i = 0; i < 125; ++i)
               counter.increment();
     };
//     std::for_each(begin(threads),end(threads),[&operation](auto& val) { val = std::thread{operation}; });
     std::cout << "Incrementing Counter (" << std::setw(3) << counter.value() << ") concurrently...";
     for (auto& t : threads)
     {
          t = std::thread{operation};
     }

     for (auto& t : threads)
          t.join();
     std::cout << " new value == " << counter.value() << '\n';

     auto second_operation = [&counter]()
     {
          for (std::size_t i = 0; i < 125; ++i)
          {
               try
               {
                    counter.decrement();
               }
               catch(const std::exception& e)
               {
                    std::cerr << "\n***Exception while trying to decrement : " << e.what() << "***\n";
               }
          }
     };

     std::cout << "Decrementing Counter (" << std::setw(3) << counter.value() << ") concurrently...";
     for (auto& t : threads)
          t = std::thread{second_operation};
     for (auto& t : threads)
          t.join();
     std::cout << " new value == " << counter.value() << '\n';

     return 0;

异常处理似乎按预期工作,我理解它的方式 std::lock_guard 应该保证一旦 lock_guard 超出范围就解锁互斥锁。

但它似乎比这更复杂。虽然增量正确导致最终值为“500”,但减量 - 应该导致“0” - 不起作用。结果将介于“0”和“16”之间。

如果时间发生变化,例如使用 valgrind,它似乎每次都能正常工作。

我能够查明使用 std::lock_guard 的问题。如果我将 decrement() 函数定义为:

      void decrement() noexcept
      {
           mutex_.lock();
           --value_;                      // possible underflow
           mutex_.unlock();
      }

一切正常(只要没有下溢)。 但是一旦我做了一个简单的改变:

      void decrement() noexcept
      {    
           std::lock_guard<std::mutex>{mutex_};
           --value_;                      // possible underflow
      }

行为就像我上面描述的那样。我想我并没有真正理解 std::lock_guard 的行为和用例。如果您能指出我正确的方向,我将不胜感激!

程序通过clang++ -std=c++14 -stdlib=libc++ *.cpp -pthread编译。

【问题讨论】:

  • @DarkFalcon 这就是我们想要的。如果该值已经为 0,则会引发异常,客户端尝试减小该值。在初始化类实例的方式中,它只能为 0 或更大(size_t 无论如何都应该是无符号的),所以如果它是 0,它应该保持不变大大地。这个问题实际上并不是要尝试实现一个完美的计数器,而是要了解 std::unique_lock 的工作原理。
  • 如果您使用原子增量/减量而不是锁定互斥锁,您可能会获得更好的性能。

标签: c++ multithreading c++11 thread-safety


【解决方案1】:

std::lock_guard&lt;std::mutex&gt;{mutex_}; 不创建本地。它创建了一个临时的,在语句结束时被销毁。这意味着您的价值不受锁的保护。锁守卫必须是本地的:

void decrement() noexcept
{    
   std::lock_guard<std::mutex> guard {mutex_};
   --value_;                      // possible underflow
}

【讨论】:

  • 这解释了它。非常感谢您的快速回复!
【解决方案2】:

问题在于这条线

std::lock_guard<std::mutex>{mutex_};

不创建变量,而是创建一个临时的lock_guard 对象,该对象会立即再次被销毁。你可能打算写的是:

std::lock_guard<std::mutex> guard{mutex_};

这将创建一个类型为 lock_guard 的变量,名为 guard,当它离开作用域时(即在函数的末尾处)被销毁。基本上,您忘记命名变量了。

【讨论】:

  • 确实是这个问题,感谢您快速解决问题!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-06-03
  • 2020-10-13
  • 2014-08-23
  • 2014-08-11
  • 2016-09-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多