Geant4 实现 Multi-Thread
关于多线程的认识
在多核多线程计算机上,可以使用多线程来提高Geant4计算效率。Geant4最新版本(10.06.p02)中提供了MT的运行实例(/examples/basic/B1),但在实际应用需要针对具体情况进行修改,我自己在做项目时也需要用到多线程,零零碎碎修改了很多地方,也从网上找了很多资料。下面总结了一些我遇到的修改问题,希望对大家有所帮助!
基础知识
通常,在多线程模式下,几何和物理过程是共享的,其他类型过程是线程局部的。Event,track,step,trajectory,hits等都是线程局部的。在用户类中,用户初始化类(G4VUserDetectorConstruction,G4VUserPhysicsList和G4VUserActionInitialization)是共享的,而用户操作类和敏感探测器类是线程本地的。在构建过程中应注意类的共享性和线程本地性。
如果需要调用线程数或者对某个线程进行操作,可以利用静态方法。需要包括“G4Threading.hh”。
- 工作线程调用 G4int G4Threading :: G4GetThreadId()返回唯一的线程标识号(0或者正整数);如果主线程调用则返回-1;若在顺序模式下调用则返回-2。
- 工作线程调用 G4bool G4Threading :: IsWorkerThread()返回 true;如果主线程或顺序模式下调用则返回 false。
- 利用 G4int G4Threading :: GetNumberOfCores()可获得机器上可以调用的线程数。
Main()
开头需要包含G4MTRunManager.hh头文件
XXX.cc
#ifdef G4MULTITHREADED
#include "G4MTRunManager.hh" // 多线程管理
#else
#include "G4RunManager.hh"
#endif
在声明和定义runManager时可以在直接确定线程数、通过交互命令或者宏命令设置,下面是交互设置:
#ifdef G4MULTITHREADED
G4MTRunManager* runManager = new G4MTRunManager;
G4int nThreads = G4Threading::G4GetNumberOfCores(); //查看计算机所有线程数
G4cout<<" Total Threads is "<< nThreads<<G4endl;
if (argc==3) {
nThreads = G4UIcommand::ConvertToInt(argv[2]); //通过交互输入nThread值
runManager->SetNumberOfThreads(nThreads);
}
#else
G4RunManager* runManager = new G4RunManager;
#endif
宏命令设置(run.mac):
/run/numberOfThreads 4 //四线程
/run/initialize
其他定义均与顺序模式一致。
PrimaryGeneratorAction
PrimaryGeneratorAction 是线程局部类。如果需要输入文件发射粒子,那所有线程都会打开同一个文件并从头开始读取该文件。
我所做的项目起初需要从同一个文件中读取粒子信息再进行发射,故在分配多线程时会遇到相空间文件关闭报错的问题。解决办法是使用Mutex锁定机制,需要包含 G4AutoLock.hh 。
PrimaryGeneratorAction.cc
#include "G4AutoLock.hh"
namespace {
G4Mutex Acc_PrimGenMutex = G4MUTEX_INITIALIZER;
G4Mutex Acc_PrimGenDestrMutex = G4MUTEX_INITIALIZER;
}
在初始化实例时需要使用 lock 锁进行输入(这里读取的是IAEA相空间文件):
Acc_PrimaryGeneratorAction::Acc_PrimaryGeneratorAction()
: G4VUserPrimaryGeneratorAction()
{
G4AutoLock lock(&Acc_PrimGenDestrMutex);
if(!theIAEAReader)
{
const char* filename = "PSF_267.8";
theIAEAReader = new G4IAEAphspReader(filename);
}
lock.unlock();
}
在析构函数中要释放指针:
Acc_PrimaryGeneratorAction::~Acc_PrimaryGeneratorAction()
{
G4AutoLock lock(&Acc_PrimGenDestrMutex);
if(theIAEAReader) { delete theIAEAReader; theIAEAReader=0; }
}
对每个Event发射粒子时需要加上lock锁:
void Acc_PrimaryGeneratorAction::GeneratePrimaries(G4Event* anEvent)
{
G4AutoLock lock(&Acc_PrimGenMutex);
theIAEAReader->GeneratePrimaryVertex(anEvent); // 开始发射
}
Sensitive detector
用户定义的灵敏探测器类对象是线程局部的,此类线程局部类的实例化应在 ConstructSDField() 中实现,在该函数类下定义的对象可以被每个线程调用。在 Construct() 中定义几何体的材料可视化属性等,但要在 ConstructSDField() 中定义敏感探测器。
若未调用多线程,在ConstructSDField()中定义的敏感探测器一样
DetectorConstruction.cc
void Acc_Head::ConstructSDandField()
{
G4MultiFunctionalDetector* myScorer = new G4MultiFunctionalDetector("PhantomSD");
G4SDManager::GetSDMpointer()->AddNewDetector(myScorer);
SetSensitiveDetector("phantomSensLV",myScorer);
}
G4VUserActionInitialization
此类供用户实例化用户操作类,所有用户操作类都是线程本地的。在 G4VUserActionInitialization 中通过 Build() 和 BuildForMaster() 来进行实例化,前者为工作线程以及顺序模式定义用户操作类,后者仅为主线程定义UserRunAction。
Actioninitialization.cc
//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......
void Acc_ActionInitialization::BuildForMaster() const
{
Acc_RunAction* runAction = new Acc_RunAction(); // 主线程对run设置用户行为
SetUserAction(runAction);
}
void Acc_ActionInitialization::Build() const
{
SetUserAction(new Acc_PrimaryGeneratorAction)// 单线程分别对粒子发射、run、event、step进行初始化
Acc_RunAction* runAction = new Acc_RunAction();
SetUserAction(runAction);
Acc_EventAction* eventAction = new Acc_EventAction;
SetUserAction(eventAction);
Acc_SteppingAction* stepAction = new Acc_SteppingAction;
SetUserAction(stepAction);
}
Run.cc
在Event循环结束时需要通过工作线程收集数据。该类定义是G4Run的派生类,需要有以下函数:
void RecordEvent(const G4Event*):针对线程本地的Event进行操作,例如合并。
void Merge(const G4Run*):将本地Run对象合并到全局Run对象。
Run.hh
#include "G4Run.hh"
#include "G4Event.hh"
#include "G4THitsMap.hh"
#include <vector>
class Acc_Run : public G4Run
{
public:
Acc_Run();
virtual ~Acc_Run();
public:
virtual void RecordEvent(const G4Event*);
virtual void Merge(const G4Run*);
void DumpAllScorer(G4int); //输出函数
private:
G4int fNx, fNy, fNz ;
//G4int nEvent;
G4int totalDoseID;
G4THitsMap<G4double> totalDose;
};
#endif
Run.cc
void Acc_Run::RecordEvent(const G4Event* evt)
{
G4HCofThisEvent*HCE = evt->GetHCofThisEvent();
if (!HCE) return;
G4THitsMap<G4double>* eventTotalDose = (G4THitsMap<G4double>*)(HCE->GetHC(totalDoseID));
totalDose += *eventTotalDose;
G4Run::RecordEvent(evt);
}
void Acc_Run::Merge(const G4Run * run)
{
const Acc_Run * localRun = static_cast<const Acc_Run*>(run);
totalDose += localRun->totalDose;
G4Run::Merge(run);
}
上述定义的Run类必须在用户的RunAction类中实例化:
RunAction.cc
G4Run* Acc_RunAction::GenerateRun()
{
// SUSANNA
// Generate new RUN object, which is specially
// dedicated for MultiFunctionalDetector scheme.
return (new Acc_Run());
}
void Acc_RunAction::EndOfRunAction(const G4Run* aRun)
{
const Acc_Run* theRun = static_cast <const Acc_Run*> aRun;
//主线程返回 True
//if (IsMaster())
}
总结
多线程最主要考虑输入和输出,在修改时可能会有很多奇怪的错误,建议大家一步步按照程序执行顺序进行排查!
如果本帖有错误和需要更正的地方,欢迎大家指出!希望大家共同进步!