C STD ::线程中的种族状况或内存损坏

Race condition or memory corruption in c++ std::thread

本文关键字:内存 损坏 STD 线程      更新时间:2023-10-16

我遇到了麻烦,以查明竞赛状况或内存损坏的确切来源。我在代码之后显示了我解决问题的尝试。

我有以下结构:

class A
{
protected:
   // various variables
   // 1. vector that is assigned value on B, C, D constructor and not 
   //   modified while in thread
   // 2. various ints
   // 3. double array that is accessed by B, C, D
   // here that are used by B, C and D
public:
   virtual void execute() = 0;
};
class B : A
{
public:
   B(...){};
   bool isFinished();
   void execute(); //execute does a very expensive loop (genetic algorithm)
}
class C : A
{
public:
   C(...){};
   bool isFinished();
   void execute();
}
class D : A
{
public:
   D(...){};
   bool isFinished();
   void execute();
}
class Worker
{
private:
   A& m_a;
   Container& m_parent;
public:
   // Worker needs a reference to parent container to control a mutex 
   // in the sync version of this code (not shown here)
   Worker(A& aa, Container& parent) : m_a(aa), m_parent(parent) {}
   executeAsynchronous();
}
class Container
{
private:
   std::vector<Worker> wVec;
public:
   addWorker(Worker w); //this does wVec.push_back(w)
   start();
}
void Worker::executeAsynchronous(){
    while(!a.isFinished())
        m_a.execute();
}
void Container::start(){
    std::thread threads[3];
    for (int i=0; i<wVec.size(); i++){
        threads[i] = std::thread(&Worker::executeAsynchronous,
                                                std::ref(wVec[i]));
    }
    for (int i=0; i<wVec.size(); i++){
        threads[i].join();
    }
}

要运行代码,我会做:

Container container;
B b(...);
C c(...);
D d(...);
Worker worker1(b, container);
Worker worker2(c, container);
Worker worker3(d, container);
container.addWorker(worker1);
container.addWorker(worker2);
container.addWorker(worker3);
container.start();

代码应该源于异步运行 execute(),但是我有以下2个问题:

  1. 一个线程比2或3或4个线程快具有更好的结果(通过在1个线程中运行遗传算法而产生的更好的优化(,我读到我可以受到限制通过内存带宽,但是那在哪里?我如何验证这种情况?

  2. 两个或多个线程:结果变得非常糟糕,某种程度上有些事情在途中被损坏或陷入困境。但是我无法确定它。我已经从代码的各个位置进行了cout,每个线程完全执行一个继承的类的execute(),即每个线程运行B, C or Dexecute(),并且不会跳跃或干扰他人。当我将m_parent.mutex.lock()m_parent.mutex.unlock()放在a.execute();周围的那一刻,有效地使多线程代码单线程变得正确。

我试图:

  1. Container推回CC_11的向量后,删除B, C and D中可能会悬挂的指针。我现在将副本传递给push_back
  2. 使用emplace_back代替push_back,但没有区别
  3. 使用vector.reserve()避免重新分配和丢失参考,但没有差异
  4. 使用 std::ref(),因为我发现了std ::螺纹制作副本,我希望修改元素 wVec[i],以前我只是将 wVec[i]传递给线程。

我相信,通过在上面做1-4,它们没有区别,并且通过单线线程运行代码,并且可以很好地工作,这并不是某种范围的情况。另外,线程或容器之间没有数据交换,我知道std::vector不是线程安全。

如果您花时间帮助我弄清楚这一点。

edit1:根据康斯坦丁的通知,这是我的RandomNumberGenerator类,它是一个静态类,我使用RandomNumberGenerator::getDouble(a,b)

称其为静态类别
//rng.h
class RandomNumberGenerator
{
private:
    static std::mt19937 rng;
public:
    static void initRNG();
    static int getInt(int min, int max);
    static double getDouble(double min, double max);
};
//rng.cpp
std::mt19937 RandomNumberGenerator::rng;
void RandomNumberGenerator::initRNG()
{
    rng.seed(std::random_device()());
}
int RandomNumberGenerator::getInt(int min, int max)
{
    std::uniform_int_distribution<std::mt19937::result_type> udist(min, max);
    return udist(rng);
}
double RandomNumberGenerator::getDouble(double min, double max)
{
    std::uniform_real_distribution<> udist(min, max);
    return udist(rng);
}

edit2:我解决了损坏问题。这是我错过的非线程安全功能的呼吁(评估功能(。至于缓慢,在线程中运行时,程序仍然很慢。我已经运行了Valgrind的callgrind,并使用gprof2dot绘制了结果,并且看来M4RC的建议保留。有很多STL容器调用,我将尝试动态分配数组。

edit3:正如康斯坦丁·潘(Constantin Pan(指出的那样,RNG类是罪魁祸首。使用gprof

进行了剖析
Flat profile:
Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total           
 time   seconds   seconds    calls   s/call   s/call  name    
 17.97     70.09    70.09  1734468     0.00     0.00  std::mersenne_twister_engine //SYNC
 18.33     64.98    64.98  1803194     0.00     0.00  std::mersenne_twister_engine //ASYNC
  6.19     63.41     8.93  1185214     0.00     0.00  std::mersenne_twister_engine //Single thread

edit4 :Deque容器也有罪-M4RC

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total           
 time   seconds   seconds    calls   s/call   s/call  name    
14.15     28.60    28.60 799662660     0.00     0.00  std::_Deque_iterator

sice涉及遗传算法,请确保随机数生成器是螺纹保护的。过去,我用cstdlibrand()来打击了这个(放缓和不正确的结果(。