Multithreading and user-defined class

_Geant4 Version: 11.2.0
_Operating System: Almalinux 9.4
_Compiler/Version: g++ (GCC) 13.1.0
_CMake Version: cmake version 3.26.2

Dear experts,

I’m developing a personal class for storing information on a ROOT ntuple. To access variables during events I’m using an Instance() method. Below you can see a snippet of the header file containing the Instance method implementation

class CreateTree
{
private:
  
  TTree*  ftree ;
  TString fname ;
  
public:
  
  CreateTree (TString name);
  ~CreateTree () ;
  
  static CreateTree* Instance() { return fInstance; } ;
  static CreateTree* fInstance;

 int aSimpleCounter;  

Now the variable aSimpleCounter is an optical photon counter that I implement in SteppingAction :

    if(G4StrUtil::contains(thePrePVName, "Calorimeter") &&  G4StrUtil::contains(thePostPVName, "SiPM_C")){  
      if (processName == "Scintillation") {
    
        CreateTree::Instance()->aSimpleCounter +=1;
        fEventAction->anotherSimpleCounter +=1;
      }

Here anotherSimpleCounter is implemented in EventAction class and fills a ROOT ntuples created via AnalysisManager class.

So, I discovered that when using more threads the two variables are different. I just found out that there are methods to avoid this issue, but since I’m not an expert I’m asking for an help to use my class with multithreading.

Thanks for the attention.

Best regards,
Francesco

1 Like

If you’re not familiar with multithreading, you probably want to get some good general texts on POSIX threads and C++ thread-safe programmng guides.

For Geant4 in particular, see Parallelism in Geant4: multi-threading capabilities — Book For Toolkit Developers 11.2 documentation.

In your code above, you don’t show how fInstance is actually initialized. As written, you’ve made it a global singleton. That means all your threads call into the same one object. If two threads try to call the same function at (almost) the same time, they will stomp on each other. This is called a race condition. If you really need this to be a global singleton, then you need to introduce mutexes, so that the threads will politely wait for each other.

ROOT is not generally thread safe when used by external software. So you will need to use a global singleton with mutexes if you are doing your own ROOT I/O.

If your N-tuple is not complicated – if it is just a simple row-wise N-tuple using TTree and TBranch with ints and doubles – you don’t actually need ROOT at all! The G4Analysis package within Geant4 can handle creating and filling your N-tuple, and writing it out in ROOT’s own format. G4Analysis is fully thread-safe and will take care of all the stuff I mentioned above.