Следующий код имитирует большую программу, которая создает экземпляр симуляции и затем распараллеливает его, используя firstprivate
, чтобы сделать мои экземпляры приватными. Однако сам экземпляр создает еще два экземпляра в своих методах.
Структура кажется надуманной, но мои руки несколько связаны: классы и их зависимость продиктованы мне инструментами, которые я хотел бы использовать, и я думаю, что этот сценарий распространен в научном компьютерном сообществе.
Он прекрасно компилируется и при ручном тестировании оказывается поточно-ориентированным и работает.
Но я не уверен, что использую технологию C ++ оптимальным образом, так как подозреваю, что мог бы объявить экземпляры дальше в иерархии памяти и избавить себя от необходимости использовать переменную static
, возможно, передавая экземпляр, созданный в parallel
регион по ссылке на другой мой экземпляр или что-то в этом роде. Я подозреваю, что это лучше, потому что все в скобках #pragma omp parallel {}
является локальным для потока.
Следовательно, моя цель состоит в том, чтобы создать два (или более) локальных для потока независимых экземпляра каждого класса и, в частности, GenNo, поскольку он моделирует генератор случайных чисел, который должен быть засеян один раз на поток, а затем просто вызываться с помощью то же самое семя, хотя здесь я изменяю то, что я называю «семенем» предсказуемым образом, просто чтобы понять поведение программы и выявить нарушения условий безопасности / гонки.
Код, который закомментирован, не работал, но вызвал «ошибку сегментации» при выходе из программы с SIGSEGV 11.
Я считаю, что «уникальный» указатель не так уникален при параллельном развертывании. В целом, это решение кажется более элегантным, и я хотел бы, чтобы оно работало, но я рад выслушать ваши комментарии.
Чтобы получить функциональность std::unique_ptr
, необходимо закомментировать строку, начинающуюся с thread_local static
, а остальные комментарии удалить.
#include <iostream>
#include <omp.h>
//#include <memory>
class GenNo
{
public:
int num;
explicit GenNo(int num_)
{
num = num_;
};
void create(int incr)
{
num += incr;
}
};
class HelpCrunch{
public:
HelpCrunch() {
}
void helper(int number)
{
std::cout << "Seed is " << number << " for thread number: " << omp_get_thread_num() << std::endl;
}
};
class calculate : public HelpCrunch
{
public:
int specific_seed;
bool first_run;
void CrunchManyNos()
{
HelpCrunch solver;
thread_local static GenNo RanNo(specific_seed);
//std::unique_ptr<GenNo> GenNo_ptr(nullptr);
/*
if(first_run == true)
{
GenNo_ptr.reset(new GenNo(specific_seed));
first_run = false;
}
solver.helper(GenNo_ptr->num);
*/
RanNo.create(1);
solver.helper(RanNo.num);
//do actual things that I hope are useful.
};
};
int main()
{
calculate MyLargeProb;
MyLargeProb.first_run = true;
#pragma omp parallel firstprivate(MyLargeProb)
{
int thread_specific_seed = omp_get_thread_num();
MyLargeProb.specific_seed = thread_specific_seed;
#pragma omp for
for(int i = 0; i < 10; i++)
{
MyLargeProb.CrunchManyNos();
std::cout << "Current iteration is " << i << std::endl;
}
}
return 0;
}
Теперь вывод с ключевым словом thread_local static
был:
Seed is 2 for thread number: 1
Current iteration is 5
Seed is 3 for thread number: 1
Current iteration is 6
Seed is 4 for thread number: 1
Current iteration is 7
Seed is 5 for thread number: 1
Current iteration is 8
Seed is 6 for thread number: 1
Current iteration is 9
Seed is 1 for thread number: 0
Current iteration is 0
Seed is 2 for thread number: 0
Current iteration is 1
Seed is 3 for thread number: 0
Current iteration is 2
Seed is 4 for thread number: 0
Current iteration is 3
Seed is 5 for thread number: 0
Current iteration is 4
Пока без использования thread_local
, но с сохранением static
я получаю:
Seed is 2 for thread number: 1
Current iteration is 5
Seed is 3 for thread number: 1
Current iteration is 6
Seed is 4 for thread number: 1
Current iteration is 7
Seed is 6 for thread number: 1
Current iteration is 8
Seed is 7 for thread number: 1
Current iteration is 9
Seed is 5 for thread number: 0
Current iteration is 0
Seed is 8 for thread number: 0
Current iteration is 1
Seed is 9 for thread number: 0
Current iteration is 2
Seed is 10 for thread number: 0
Current iteration is 3
Seed is 11 for thread number: 0
Current iteration is 4
Если я вообще пропущу ключевое слово static
, экземпляр просто будет переназначаться, и, хотя я сильно подозреваю, что он останется закрытым для потока, он малопригоден, так как счетчик застрянет на 1 или 2 для резьбы 0 и 1 на двухъядерной машине. (Реальное приложение должно быть в состоянии «подсчитывать» и не подвергаться воздействию параллельного потока.)
Что мне нужно помочь с
Теперь я смоделировал пример так, чтобы нарушение безопасности потока стало очевидным из-за того, что счетчики мешали друг другу, как мы можем видеть, это тот случай, когда thread_local опущен, а static оставлен в (ни то, ни другое глупо). ). Класс HelpCrunch
на самом деле намного сложнее и, скорее всего, ориентирован на многопотоковое исполнение и прекрасно подходит для повторной инициализации при каждом повторении цикла. (На самом деле это лучше, потому что он выбирает кучу переменных из своего дочернего элемента, который является частным экземпляром.) Но как вы думаете, было бы лучше добавить thread_local к созданию solver
, без ключевого слова static
? Или я должен объявить экземпляр в другом месте, и в этом случае мне понадобится помощь с передачей по указателю / ссылке и т. Д.