MICC JINR Multifunctional
Information and Computing
Complex

RU

Multithreading and multiprocessing

 

 
 

To increase the productivity of calculations, ROOT offers a tool whose work is based on the parallelism of distributed systems, i.e. PROOF. On the other hand, solutions related to simple multithreading and multiprocessing data processing is often desirable. Methods that provide these properties appeared in ROOT beginning with Version 6.08. To make these features available, ROOT must be compiled with the imt = ON flag. When working on the HybriLIT cluster, a user must install the ROOT/v6-13-02-1 module.

MultithreadingROOT can be used as a library from several threads, provided that the special function ROOT::EnableImplicitMT (numthreads) is used. This function ensures the global inclusion of implicit multithreading in ROOT and the activation of parallel execution of methods in ROOT, which provide internal parallelization.

The 'numthreads' parameter allows you to control the number of threads used by implicit multithreading. However, this parameter is only asks ROOT to set such a number of threads, and ROOT will try to satisfy the request if the execution of the script allows doing it. And if ROOT is configured to use an external scheduler, setting the value 'numthreads' may have no effect. ROOT:: EnableImplicitMT (numthreads) is called before performing operations in multiple threads.

The ROOT::EnableThreadSafety()function provides a global mutex to make ROOT thread-safe.

A mutex (i.e. mutual exclusion) is an analogue of a single semaphore, which is used in programming to synchronize simultaneously running threads. A mutex differs from a semaphore in that only the thread that owns it can release it, i.e. translate to the marked state. Mutexes are one of the variants of semaphore mechanisms for organizing mutual exclusion. They are implemented in many operating systems; their main purpose is the organization of mutual exclusion for threads from the same or from different processes. Mutexes are the simplest binary semaphores that can be in one of two states marked or unmarked (open and closed respectively). When a thread belonging to any process becomes the owner of a mutex object, the latter is transferred to the unmarked state. If the task releases the mutex, its state becomes marked. The task of the mutex is to protect the object from access to it by other threads other than the one that has taken possession of the mutex. At any given moment, only one thread can own an object protected by the mutex. If another thread needs access to a variable protected by the mutex, then this thread is blocked until the mutex is released. The purpose of using mutexes is to protect data from damage due to asynchronous changes.

The ROOT::DisableImplicitMT () and ROOT::IsImplicitMTEnabled() methods are used to disable and check the state of global implicit multithreading in ROOT, respectively. It is also necessary that only one file is read or written in each thread.

Multiprocessing

The TProcPool class is a tool provided by ROOT for simple operations using multiprocessing. A new interface implemented in the TProcPool class provides the ability to perform a very common set of tasks in parallel, described by macros or functions. The main methods of the class, i.e. TProcPool::Map (F func, unsigned nTimes) and TProcPool::MapReduce (F func, unsigned nTimes, R redfunk), analyze trees using all available kernels.

Here are tutorials designed to illustrate multi-core features of ROOT, such as thread awareness and security, multithreading and multiprocessing. 

The program below demonstrates how to activate and use implicit paralleling of the TTree::GetEntry() method.

int imt001_parBranchProcessing()

{

  // First enable implicit multithreading to use implicit parallelization.

  // The call parameter defines the number of threads.

  int nthreads = 4;

  ROOT::EnableImplicitMT(nthreads);

  // Open the file containing the tree

  TFile *file = TFile::Open("http://root.cern.ch/files/h1/dstarmb.root");

  // Get the tree

  TTree *tree = nullptr;

  file->GetObject("h42", tree);

  // Read the branches in parallel.

  // Note that the interface does not change, parallelization is internal

  for (Long64_t i = 0; i < tree->GetEntries(); ++i) {

     tree->GetEntry(i);  // parallel read

  }

  // IMT parallelization can be disabled for a specific tree

  tree->SetImplicitMT(false);

  // If now call GetEntry, reading will be sequential

  for (Long64_t i = 0; i < tree->GetEntries(); ++i) {

     tree->GetEntry(i);  // sequential reading

  }

  // Parallel reading can be restored

  tree->SetImplicitMT(true);

  // IMT can be also disabled globally.

  // As a result, no tree will run GetEntry in parallel

  ROOT::DisableImplicitMT();

  // In this case, even if a particular tree is allowed to read in parallel,

  // reading will occur sequentially

  for (Long64_t i = 0; i < tree->GetEntries(); ++i) {

     tree->GetEntry(i);  // sequential reading

  }
  return 0; 

}

Such parallelization creates one task for each branch of the top level of the analyzed tree. In this example, most of the branches are floating-point numbers, which are read very quickly. However, this parallelization can also be used on large trees with many (complex) branches. In this case, the benefits of acceleration will be more obvious.