c++ 中的竞争条件或内存损坏 std::thread
Race condition or memory corruption in c++ std::thread
我无法确定竞争条件或内存损坏的确切来源。代码后显示了我解决问题的尝试。
我有以下结构:
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 个或 3 个或 4 个线程快 AND 有更好的结果(更好的优化来自 运行 中的遗传算法1 个线程),我已经读到 I could be limited by memory bandwidth 但这是在哪里发生的?我怎样才能验证是这种情况?
两个或更多线程:结果变得非常糟糕,不知何故某些东西在途中被破坏或损坏。但是我无法确定它。我从代码中的不同位置 cout
ed 并且每个线程恰好执行一个继承的 class 的 execute()
即每个线程 运行s 的 execute()
B, C or D
并且不会跳跃或干扰他人。当我将 m_parent.mutex.lock()
和 m_parent.mutex.unlock()
放在 a.execute();
周围时,有效地使多线程代码成为单线程,结果再次正确。
我尝试过:
- 删除
B, C and D
中的指针,这些指针在将 Workers
推回 Container
的向量后可能会悬空。我现在将副本传递给 push_back
。
- 使用
emplace_back
而不是 push_back
但没有区别
- 使用
vector.reserve()
避免重新分配和丢失引用但没有区别
- 使用
std::ref()
因为我发现 std::thread 制作了一个副本并且我想要修改元素 wVec[i]
,之前我只是将 wVec[i]
传递给线程。
我相信通过执行上面的 1-4 并且它们没有任何区别,并且通过 运行 单线程代码并且它完美地工作,这不是超出范围的情况。
线程或容器之间也没有数据交换,我知道 std::vector
不是线程安全的。
如果你能花时间帮我解决这个问题,我将不胜感激。
EDIT1: 根据 Constantin Pan 的通知,这是我的 RandomNumberGenerator class,它是静态的 class,我用 [=35] 调用它=]
//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: 看起来 RNG class 是罪魁祸首,正如 Constantin Pan 指出的那样。使用 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:双端队列容器也有罪 - 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
由于涉及遗传算法,请确保随机数生成器是线程安全的。我过去曾用 cstdlib
.
中的 rand()
遇到过这个问题(减速和不正确的结果)
我无法确定竞争条件或内存损坏的确切来源。代码后显示了我解决问题的尝试。
我有以下结构:
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 个或 3 个或 4 个线程快 AND 有更好的结果(更好的优化来自 运行 中的遗传算法1 个线程),我已经读到 I could be limited by memory bandwidth 但这是在哪里发生的?我怎样才能验证是这种情况?
两个或更多线程:结果变得非常糟糕,不知何故某些东西在途中被破坏或损坏。但是我无法确定它。我从代码中的不同位置
cout
ed 并且每个线程恰好执行一个继承的 class 的execute()
即每个线程 运行s 的execute()
B, C or D
并且不会跳跃或干扰他人。当我将m_parent.mutex.lock()
和m_parent.mutex.unlock()
放在a.execute();
周围时,有效地使多线程代码成为单线程,结果再次正确。
我尝试过:
- 删除
B, C and D
中的指针,这些指针在将Workers
推回Container
的向量后可能会悬空。我现在将副本传递给push_back
。 - 使用
emplace_back
而不是push_back
但没有区别 - 使用
vector.reserve()
避免重新分配和丢失引用但没有区别 - 使用
std::ref()
因为我发现 std::thread 制作了一个副本并且我想要修改元素wVec[i]
,之前我只是将wVec[i]
传递给线程。
我相信通过执行上面的 1-4 并且它们没有任何区别,并且通过 运行 单线程代码并且它完美地工作,这不是超出范围的情况。
线程或容器之间也没有数据交换,我知道 std::vector
不是线程安全的。
如果你能花时间帮我解决这个问题,我将不胜感激。
EDIT1: 根据 Constantin Pan 的通知,这是我的 RandomNumberGenerator class,它是静态的 class,我用 [=35] 调用它=]
//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: 看起来 RNG class 是罪魁祸首,正如 Constantin Pan 指出的那样。使用 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:双端队列容器也有罪 - 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
由于涉及遗传算法,请确保随机数生成器是线程安全的。我过去曾用 cstdlib
.
rand()
遇到过这个问题(减速和不正确的结果)