【问题标题】:Can't figure out where race condition is occuring无法弄清楚比赛条件发生在哪里
【发布时间】:2010-06-14 06:24:47
【问题描述】:

我正在使用 Valgrind --tool=drd 检查我使用 Boost::thread 的应用程序。 基本上,应用程序会根据通过套接字连接的输入使用“Kehai”值填充一组“Book”值。

在单独的线程上,用户可以连接并获取发送给他们的书籍。

它相当简单,所以我想在序列化书籍的位置和清除书籍数据的位置上使用 boost::mutex::scoped_lock 应该足以防止任何竞争条件。代码如下:

 void Book::clear()
    {
     boost::mutex::scoped_lock lock(dataMutex);
     for(int i =NUM_KEHAI-1; i >= 0; --i)
     {
      bid[i].clear();

      ask[i].clear();
     }
    }

    int Book::copyChangedKehaiToString(char* dst) const
    {
     boost::mutex::scoped_lock lock(dataMutex);

     sprintf(dst, "%-4s%-13s",market.c_str(),meigara.c_str());
     int loc = 17;
     for(int i = 0; i < Book::NUM_KEHAI; ++i)
     {
      if(ask[i].changed > 0)
      {
       sprintf(dst+loc,"A%i%-21s%-21s%-21s%-8s%-4s",i,ask[i].price.c_str(),ask[i].volume.c_str(),ask[i].number.c_str(),ask[i].postTime.c_str(),ask[i].status.c_str());
       loc += 77;
      }
     }
     for(int i = 0; i < Book::NUM_KEHAI; ++i)
     {
      if(bid[i].changed > 0)
      {
       sprintf(dst+loc,"B%i%-21s%-21s%-21s%-8s%-4s",i,bid[i].price.c_str(),bid[i].volume.c_str(),bid[i].number.c_str(),bid[i].postTime.c_str(),bid[i].status.c_str());
       loc += 77;
      }
     }

     return loc;
    }

数据获取线程和数据发送线程分别调用clear()函数和copyChangedKehaiToString()函数。 另外,请注意,类 Book:

    struct Book
    {
    private:
     Book(const Book&); Book& operator=(const Book&);
    public:

     static const int NUM_KEHAI=10;
     struct Kehai;
     friend struct Book::Kehai;

     struct Kehai
     {
     private:
       Kehai& operator=(const Kehai&);
     public:
      std::string price;
      std::string volume;
      std::string number;
      std::string postTime;
      std::string status;

      int changed;
      Kehai();
      void copyFrom(const Kehai& other);
      Kehai(const Kehai& other);
      inline void clear()
      {

       price.assign("");
       volume.assign("");
       number.assign("");
       postTime.assign("");
       status.assign("");
       changed = -1;
      }
     };

     std::vector<Kehai> bid;
     std::vector<Kehai> ask;
     tm recTime;
     mutable boost::mutex dataMutex;


     Book();
     void clear();
     int copyChangedKehaiToString(char * dst) const;
      };

当使用 valgrind --tool=drd 时,我得到如下竞争条件错误:

==26330== Conflicting store by thread 1 at 0x0658fbb0 size 4
==26330==    at 0x653AE68: std::string::_M_mutate(unsigned int, unsigned int, unsigned int) (in /usr/lib/libstdc++.so.6.0.8)
==26330==    by 0x653AFC9: std::string::_M_replace_safe(unsigned int, unsigned int, char const*, unsigned int) (in /usr/lib/libstdc++.so.6.0.8)
==26330==    by 0x653B064: std::string::assign(char const*, unsigned int) (in /usr/lib/libstdc++.so.6.0.8)
==26330==    by 0x653B134: std::string::assign(char const*) (in /usr/lib/libstdc++.so.6.0.8)
==26330==    by 0x8055D64: Book::Kehai::clear() (Book.h:50)
==26330==    by 0x8094A29: Book::clear() (Book.cpp:78)
==26330==    by 0x808537E: RealKernel::start() (RealKernel.cpp:86)
==26330==    by 0x804D15A: main (main.cpp:164)
==26330== Allocation context: BSS section of /usr/lib/libstdc++.so.6.0.8
==26330== Other segment start (thread 2)
==26330==    at 0x400BB59: pthread_mutex_unlock (drd_pthread_intercepts.c:633)
==26330==    by 0xC59565: pthread_mutex_unlock (in /lib/libc-2.5.so)
==26330==    by 0x805477C: boost::mutex::unlock() (mutex.hpp:56)
==26330==    by 0x80547C9: boost::unique_lock<boost::mutex>::~unique_lock() (locks.hpp:340)
==26330==    by 0x80949BA: Book::copyChangedKehaiToString(char*) const (Book.cpp:134)
==26330==    by 0x80937EE: BookSerializer::serializeBook(Book const&, std::string const&) (BookSerializer.cpp:41)
==26330==    by 0x8092D05: BookSnapshotManager::getSnaphotDataList() (BookSnapshotManager.cpp:72)
==26330==    by 0x8088179: SnapshotServer::getDataList() (SnapshotServer.cpp:246)
==26330==    by 0x808870F: SnapshotServer::run() (SnapshotServer.cpp:183)
==26330==    by 0x808BAF5: boost::_mfi::mf0<void, RealThread>::operator()(RealThread*) const (mem_fn_template.hpp:49)
==26330==    by 0x808BB4D: void boost::_bi::list1<boost::_bi::value<RealThread*> >::operator()<boost::_mfi::mf0<void, RealThread>, boost::_bi::list0>(boost::_bi::type<void>, boost::_mfi::mf0<void, RealThread>&, boost::_bi::list0&, int) (bind.hpp:253)
==26330==    by 0x808BB90: boost::_bi::bind_t<void, boost::_mfi::mf0<void, RealThread>, boost::_bi::list1<boost::_bi::value<RealThread*> > >::operator()() (bind_template.hpp:20)
==26330== Other segment end (thread 2)
==26330==    at 0x400B62A: pthread_mutex_lock (drd_pthread_intercepts.c:580)
==26330==    by 0xC59535: pthread_mutex_lock (in /lib/libc-2.5.so)
==26330==    by 0x80546B8: boost::mutex::lock() (mutex.hpp:51)
==26330==    by 0x805473B: boost::unique_lock<boost::mutex>::lock() (locks.hpp:349)
==26330==    by 0x8054769: boost::unique_lock<boost::mutex>::unique_lock(boost::mutex&) (locks.hpp:227)
==26330==    by 0x8094711: Book::copyChangedKehaiToString(char*) const (Book.cpp:113)
==26330==    by 0x80937EE: BookSerializer::serializeBook(Book const&, std::string const&) (BookSerializer.cpp:41)
==26330==    by 0x808870F: SnapshotServer::run() (SnapshotServer.cpp:183)
==26330==    by 0x808BAF5: boost::_mfi::mf0<void, RealThread>::operator()(RealThread*) const (mem_fn_template.hpp:49)
==26330==    by 0x808BB4D: void boost::_bi::list1<boost::_bi::value<RealThread*> >::operator()<boost::_mfi::mf0<void, RealThread>, boost::_bi::list0>(boost::_bi::type<void>, boost::_mfi::mf0<void, RealThread>&, boost::_bi::list0&, int) (bind.hpp:253)

对于我的一生,我无法弄清楚比赛条件在哪里。据我所知,只有在获取互斥锁后才能清除 kehai,将其复制到字符串也是如此。 有没有人有任何想法可能导致这种情况,或者我应该去哪里看?

谢谢。

【问题讨论】:

    标签: c++ boost multithreading valgrind


    【解决方案1】:

    在您的帖子之后,我花时间了解了 Valgrind 以及应该如何阅读它的输出。

    我可以看到以下内容:

    您调用Book::clear,后者又调用Book::Kehai::clear,您在其中为字符串赋值。在std::string::assign 内部,STL 会在地址 0x0658fbb0 处存储一些值。

    同时另一个线程访问了相同的内存位置,因此这种情况被认为是竞争条件。

    现在看看另一个线程的“上下文”。 Valgrind 没有显示它的确切堆栈位置,但是它显示了它发生在哪些“段”之间。根据 Valgrind,段是由同步操作限制的连续内存访问块。

    我们看到这个块开始pthread_mutex_unlock结束pthread_mutex_lock。 意味着 - 当您的互斥锁未锁定时访问了相同的内存位置,并且该线程位于您的两个函数之外。

    现在,看看有冲突的内存位置信息:

    Allocation context: BSS section of /usr/lib/libstdc++.so.6.0.8
    

    BSS 表示它是一个全局/静态变量。它是在 libstdc 中的某个地方定义的。

    结论:

    这种竞争条件与您的数据结构无关。它与STL有关。一个线程对std::string 执行某些操作(准确地说,将其分配给一个空字符串),而另一个线程也可能执行与 STL 相关的操作。

    顺便说一句,我记得几年前我写过一个多线程应用程序,那里的std::string 有问题。我发现 - STL 实现(它是一个 Dunkimware)实际上将字符串实现为引用计数,而引用计数不是线程安全的。

    也许这也发生在你身上? 也许你应该在构建多线程应用程序时设置一些编译器标志/选项?

    【讨论】:

    • valdo,感谢您的更新。是的,我认为这是同样的问题。当我将所有内容都切换为 C 风格的字符串时,错误就消失了。
    • 这是一篇有用的帖子。现在我想用 Valgrind 测试我的应用程序,我大量使用多线程。关于字符串:您说当您切换到“C 样式字符串”时问题就消失了。但是您所说的“C 风格字符串”是什么?更准确地说-您对它们的操纵和寿命的政策是什么。我相信这个问题与你的字符串是否被引用计数有关。意味着-当您将一个字符串分配给另一个时:您实际上是创建字符串的副本,还是分配另一个与该字符串相同的副本?如果它是引用计数的 - 它是线程安全的吗?
    • STL 应该是线程安全的,因为如果您正确锁定或仅执行多线程读取,将它们与线程一起使用不会有问题
    【解决方案2】:

    可以安全地忽略此报告。它由 std::string 在 libstdc++ 中的实现方式触发。此问题已在 gcc 4.4.4 或更高版本中包含的 libstdc++ 版本中得到解决。详情请参阅GCC bugzilla item #40518

    【讨论】:

      【解决方案3】:

      没关系。我是个白痴,并设法忘记了 C++ 字符串是可变的。 我将代码更改为使用 c 风格的字符串,并且我的竞争条件问题消失了。

      顺便问一下,对于阅读这篇文章的人来说,有没有人知道一个好的 C++ 不可变字符串库?我以为 boost 有一个,但我还没有找到任何结论性的东西。

      谢谢。

      【讨论】:

      • 如果一切都得到正确保护,我已经使用线程和 std::strings(或 STL 容器..)没有任何问题。你使用什么编译器/STL?
      • 这是 C++ stl 字符串的一个已知问题。由于它们使用引用计数并且是可变的,因此这个问题似乎经常出现。在 Centos 5 上使用 gcc 4.1
      • 根据我对另一个回复的评论,您可以强制 STL 复制而不是克隆 [string2 = string1.c_str()]。你甚至可以创建一个“线程安全的字符串持有者”类来封装这个变通方法,并在所有存储字符串的共享对象中使用——这样就很难忘记做对了,如果你切换到更容易改变实现一个简单的 STL。
      【解决方案4】:

      STL 应该是线程安全的,因为如果您正确锁定或仅执行多线程读取,将它们与线程一起使用不会有问题

      惊喜!是的,假设,但让我告诉你一家商店发生了什么

      我有一个多线程应用程序。有一个带有字符串的数据结构 (std::string)。它被临界区锁定。

      其他对象最终需要从那里获取字符串。他们以下列方式复制了这些字符串:

      // Take a string
      std::string str;
      {
          Autolock l(g_CritSect);
          str = g_SomeStr;
      }
      

      使用相同的策略来调整这些字符串:

      // Put a string
      std::string str;
      {
          Autolock l(g_CritSect);
          g_SomeStr = str;
      }
      

      你猜怎么着?崩溃!

      但是为什么呢?因为字符串的赋值语句并没有真正生成保存字符串的内存块的副本。而是重复使用(引用)相同的内存块。

      嗯,这不一定是坏事。但坏事是 std::string 不是以线程安全的方式实现字符串的引用计数。它使用常规算术 ++ 和 -- 而不是 InterlockedIncrement 等。

      发生的事情是str 对象引用了相同的字符串。然后它最终在其析构函数中取消引用它(或者当显式分配给另一个字符串时)。这发生在锁定区域之外

      这样这些字符串就不能在多线程应用程序中使用。而且几乎不可能实现正确的锁定来解决这个问题,因为实际引用的数据在对象之间静默传递。

      并且 STL 的实现被声明为线程安全的。

      【讨论】:

      • 大概你可以强制复制实际复制,例如g_SomeStr = str.c_str() ? [显然是一个可怕的变通办法,最好切换到一个简单的 STL。]
      • 是的,总是有糟糕的实现,但并非总是如此
      猜你喜欢
      • 1970-01-01
      • 2021-06-27
      • 2019-09-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-12-02
      相关资源
      最近更新 更多