这是我的第一个问题,所以请原谅我违反政策的行为。我希望每个线程有一个全局随机数引擎,为此我设计了以下方案:我开始的每个线程从原子全局int获得一个唯一索引。存在随机引擎的静态向量,其第i个成员被认为由具有索引i的线程使用。如果索引大于矢量大小元素以同步方式添加到它。为了防止性能损失,我检查两次索引是否大于向量大小:一次是以未同步的方式,一次是在锁定互斥锁之后。到目前为止一切顺利,但以下示例因各种错误(堆损坏,malloc错误等)而失败。
#include<vector>
#include<thread>
#include<mutex>
#include<atomic>
#include<random>
#include<iostream>
using std::cout;
std::atomic_uint INDEX_GEN{};
std::vector<std::mt19937> RNDS{};
float f = 0.0f;
std::mutex m{};
class TestAThread {
public:
TestAThread() :thread(nullptr){
cout << "Calling constructor TestAThread\n";
thread = new std::thread(&TestAThread::run, this);
}
TestAThread(TestAThread&& source) : thread(source.thread){
source.thread = nullptr;
cout << "Calling move constructor TestAThread. My ptr is " << thread << ". Source ptr is" << source.thread << "\n";
}
TestAThread(const TestAThread& source) = delete;
~TestAThread() {
cout << "Calling destructor TestAThread. Pointer is " << thread << "\n";
if (thread != nullptr){
cout << "Deleting thread pointer\n";
thread->join();
delete thread;
thread = nullptr;
}
}
void run(){
int index = INDEX_GEN.fetch_add(1);
std::uniform_real_distribution<float> uniformRnd{ 0.0f, 1.0f };
while (true){
if (index >= RNDS.size()){
m.lock();
// add randoms in a synchronized manner.
while (index >= RNDS.size()){
cout << "index is " << index << ", size is " << RNDS.size() << std::endl;
RNDS.emplace_back();
}
m.unlock();
}
f += uniformRnd(RNDS[index]);
}
}
std::thread* thread;
};
int main(int argc, char* argv[]){
std::vector<TestAThread> threads;
for (int i = 0; i < 10; ++i){
threads.emplace_back();
}
cout << f;
}
我做错了什么?!
答案 0 :(得分:2)
显然f += ...
无论右手边都是竞赛条件,但我想你已经知道了。
我看到的主要问题是您使用全球std::vector<std::mt19937> RNDS
。您的受互斥锁保护的关键部分仅包含添加新元素;不访问现有元素:
... uniformRnd(RNDS[index]);
这不是线程安全的,因为在另一个线程中调整RNDS
可能会导致RNDS[index]
移动到新的内存位置。事实上,这可能发生在计算引用RNDS[index]
但uniformRnd
开始使用它之前,在这种情况下,uniformRnd
认为是Generator&
将是悬空指针,可能是新创建的对象。无论如何,uniformRnd
的{{1}}不保证数据竞赛[注1],operator()
的{{1}}也不保证。
您可以通过以下方式解决此问题:
计算受保护部分内生成器的引用(或指针)(不能取决于容器的大小是否足够),
使用RNDS
而不是operator[]
,它在调整大小时不会使引用无效(除非通过调整大小从容器中删除引用的对象)。
这样的事情(关注竞争条件;还有其他事情,我可能会采取不同的做法):
std::deque
[1] std::vector
std::mt19937& get_generator(int index) {
std::lock_guard<std::mutex> l(m);
if (index <= RNDS.size()) RNDS.resize(index + 1);
return RNDS[index];
}
void run(){
int index = INDEX_GEN.fetch_add(1);
auto& gen = get_generator(index);
std::uniform_real_distribution<float> uniformRnd{ 0.0f, 1.0f };
while (true) {
/* Do something with uniformRnd(gen); */
}
}
的原型是operator()
。换句话说,参数必须是 mutable 引用,这意味着它不是隐式线程安全的;只有标准库函数的uniformRnd
个参数没有数据竞争。