Runs the executor with n workers.

        :param workers: Number of parallel processes used in the execution. Default is a single process. If set to
            `None` the number of workers will be the number of processors of the system.
        :type workers: int or None

def run(self, workers=1):
        """ Runs the executor with n workers.

        :param workers: Number of parallel processes used in the execution. Default is a single process. If set to
            `None` the number of workers will be the number of processors of the system.
        :type workers: int or None
        """
        self.report_folder = self._get_report_folder()
        if self.save_logs and not os.path.isdir(self.report_folder):
            os.mkdir(self.report_folder)

        execution_num = len(self.execution_args)
        log_paths = [self._get_log_filename(idx) if self.save_logs else None
                     for idx in range(execution_num)]

        processing_args = [(self.workflow, init_args, log_path) for init_args, log_path in zip(self.execution_args,
                                                                                               log_paths)]

        with concurrent.futures.ProcessPoolExecutor(max_workers=workers) as executor:
            self.execution_stats = list(tqdm(executor.map(self._execute_workflow, processing_args),
                                             total=len(processing_args)))

        self.execution_logs = [None] * execution_num
        if self.save_logs:
            for idx, log_path in enumerate(log_paths):
                with open(log_path) as fin:
                    self.execution_logs[idx] = fin.read()