Geant4 学习笔记(一):实现multi-thread多线程

关于多线程的认识

在多核多线程计算机上,可以使用多线程来提高Geant4计算效率。Geant4最新版本(10.06.p02)中提供了MT的运行实例(/examples/basic/B1),但在实际应用需要针对具体情况进行修改,我自己在做项目时也需要用到多线程,零零碎碎修改了很多地方,也从网上找了很多资料。下面总结了一些我遇到的修改问题,希望对大家有所帮助!

基础知识

通常,在多线程模式下,几何和物理过程是共享的,其他类型过程是线程局部的。Event,track,step,trajectory,hits等都是线程局部的。在用户类中,用户初始化类(G4VUserDetectorConstructionG4VUserPhysicsListG4VUserActionInitialization)是共享的,而用户操作类和敏感探测器类是线程本地的。在构建过程中应注意类的共享性线程本地性

如果需要调用线程数或者对某个线程进行操作,可以利用静态方法。需要包括“G4Threading.hh”。

  1. 工作线程调用 G4int G4Threading :: G4GetThreadId()返回唯一的线程标识号(0或者正整数);如果主线程调用则返回-1;若在顺序模式下调用则返回-2。
  2. 工作线程调用 G4bool G4Threading :: IsWorkerThread()返回 true;如果主线程或顺序模式下调用则返回 false。
  3. 利用 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()) 
}

总结

多线程最主要考虑输入和输出,在修改时可能会有很多奇怪的错误,建议大家一步步按照程序执行顺序进行排查!

如果本帖有错误和需要更正的地方,欢迎大家指出!希望大家共同进步!

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值