code: def concurrent_exec(func, param_list):
      
      with concurrent.futures.ThreadPoolExecutor(max_workers=30) as executor:
          # Start the load operations and mark each future with its params
          future_to_params = {executor.submit(func, *p): p for p in param_list}
          return_vals = []
          for future in concurrent.futures.as_completed(future_to_params):
              params = future_to_params[future]
              try:
                  return_vals.append(future.result())
              except Exception as exc:
                  logging.exception("{} generated an exception: {}".format(
                      params, traceback.format_exc()))
                  return_vals.append(exc)
          return return_vals
docs: Executes a function with different parameters pseudo-concurrently.
  
      This is basically a map function. Each element (should be an iterable) in
      the param_list is unpacked and passed into the function. Due to Python's
      GIL, there's no true concurrency. This is suited for IO-bound tasks.
  
      Args:
          func: The function that parforms a task.
          param_list: A list of iterables, each being a set of params to be
              passed into the function.
  
      Returns:
          A list of return values from each function execution. If an execution
          caused an exception, the exception object will be the corresponding
          result.