Hi Giorgio,
avoiding crashes when using ROOT objects from multiple threads does not depend on the framework you use (e.g. std::thread vs boost.asio) as much as how you use the ROOT objects involved. With the exception of ROOT TThread, which should be considered deprecated, and PROOF, which afaik is multi-process, not multi-thread, other options are fairly equivalent. There is another option besides those you list, which is taking advantage of ROOT’s TThreadExecutor and TTreeProcessorMT to schedule tasks directly on ROOT’s internal thread pool. A large multi-thread framework that uses task-based parallelism with ROOT is for example CMSSW.
In 1995, ROOT was not designed with multi-thread usage in mind; in particular, ROOT keeps track of a lot of global state, think gROOT
and its GetListOf*
methods: those are all global lists. However, there are common usage patterns that have been made thread-safe specifically to aid the development of frameworks like yours. ROOT itself offers multi-thread TTree processing tools such as RDataFrame and TTreeProcessorMT that take advantage of such guarantees.
To make such common usage patterns safe, you have to call EnableThreadSafety: its docs list what exactly is made thread-safe when calling it.
In particular, for what regards TFile: you can open separate TFiles in separate threads concurrently, and each TFile should only be used and closed from the thread that opened it. Constructing, using and destructing separate TTree and TChain objects in each thread is also safe if ROOT::EnableThreadSafety has been called.
So all requirements look satisfied or satisfiable with ROOT 6.18+. The tricky part will probably be the merging step. Depending on what you mean exactly with “merging”, and how the TTrees are handled, it might or might not be safe. For example, you can use Experimental::TBufferMerger to write to a single output TFile from multiple threads: this is how multi-thread writing of TTrees is implemented in RDataFrame. A small example snippet that shows the usage you have in mind might help us help you further.
Cheers,
Enrico
EDIT:
as @joa says, spawning multiple single-thread processes and then merging their outputs in a final single-thread merging step is a valid strategy to avoid all shared state issues. Depending on your usecase, it might just be the best solution, or it might come with extra runtime, memory usage and output-handling/merging overhead that is undesirable. TProcessExecutor is a facility that lets you easily spawn worker processes via fork.