【问题标题】:Danger of using std::sort with vector of shared_ptr in multi-threaded world在多线程世界中使用带有 shared_ptr 向量的 std::sort 的危险
【发布时间】:2016-03-21 06:23:08
【问题描述】:

这是我在将应用程序升级到多线程世界时遇到的问题的简化版本 (C++11)。本质上,我有 shared_ptr 的向量,并且正在对其进行 std::sort 。当多个线程尝试对其进行排序时,我可以理解,这很危险,因为在排序时,第一次迭代器可能不得不四处移动。但是,在这里,我已经有了一个排序的向量。现在调用 std::sort 它不应该带来任何麻烦(这就是我认为不需要移动的东西)但是它随机崩溃了。(现在为什么我在排序容器上调用 std::sort ,实际上,在原始代码,数据未排序,但这似乎与最终结果无关)。这是示例代码

#include <iostream>
#include <thread>
#include <vector>
#include <boost/shared_ptr.hpp>  
const int MAX = 4;
#define LOOP_COUNT 200

struct Container {
    int priority;
    Container(int priority_)
        : priority( priority_)
    {} 
};

struct StrategySorter {
    int operator()( const boost::shared_ptr<Container>& v1_,
        const boost::shared_ptr<Container>& v2_ )
    {
      return v1_->priority > v2_->priority;    
    } 
 };

std::vector<boost::shared_ptr<Container>> _creators;

void func() {    
    for(int i=0; i < LOOP_COUNT; ++i)    {
      std::sort( _creators.begin(), _creators.end(),  StrategySorter() );
    } 
}
int main()
{

   int priority[] = {100, 245, 312, 423, 597, 656, 732 };
   size_t size = sizeof(priority)/sizeof(int);

   for(int i=0; i < size; ++i)
   {
      _creators.push_back(boost::shared_ptr<Container>(new Container(priority[i])));
   }

   std::thread t[MAX];
   for(int i=0;i < MAX; i++)
   {
      t[i] = std::thread(func);
   }

   for(int i=0;i < MAX; i++)
   {
      t[i].join();
   }
}

错误: ../boost_1_56_0/include/boost/smart_ptr/shared_ptr.hpp:648: typename boost::detail::sp_member_access::type boost::shared_ptr::operator->() const [with T = Container; typename boost::detail::sp_member_access::type = Container*]: Assertion `px != 0' failed.

拥有原始指针不会使其崩溃,因此它特定于 shared_ptr。 使用互斥锁保护 std::sort 可以防止崩溃。 我无法理解为什么这种情况会导致行为不一致。

【问题讨论】:

  • 您无法保证对已排序数组进行排序不会前后移动元素。

标签: multithreading sorting c++11 boost vector


【解决方案1】:

当多个线程在没有同步的情况下访问相同的数据并且其中至少有一个正在执行修改操作时,这是一种竞争条件,因此是未定义的行为。任何事情都有可能发生。

std::sort 需要可变迭代器来操作,因此根据定义,它是一种修改操作,因此在没有同步的情况下将其同时应用于重叠范围是一种竞争条件(因此是 UB)。

【讨论】:

    【解决方案2】:

    无法保证最终不移动元素的排序不会写入。

    它可能想要移动枢轴,在中间阶段向后排序一些东西,甚至在没有自检优化的情况下调用swap(a,a)(因为检查可能比交换更昂贵)。

    在任何情况下,如果它什么都不做的操作就是 UB,那么调用它是一个可怕的操作。

    如果什么都不做,这是一种保证什么都不做的排序:

    template<class C, class Cmp>
    void my_sort( C& c, Cmp cmp ) {
      using std::begin; using std::end;
      if (std::is_sorted( begin(c), end(c), cmp ))
        return;
      std::sort( begin(c), end(c), cmp );
    }
    

    但我不会使用它。

    【讨论】:

      猜你喜欢
      • 2014-01-18
      • 2021-11-29
      • 1970-01-01
      • 2021-08-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多