text: Title: Passing a random number generator to simulator/sampler methods should VS maintaing an internal random state
  Body: All Cirq simulators maintain an internal `np.random.RandomState` https://github.com/quantumlib/Cirq/blob/e1b03ef63af4270d6a185df3db6e43c8232c6a71/cirq-core/cirq/sim/sparse_simulator.py#L130
  
  This is fine when running in a single thread, however we are starting to have more places where use  multiprocessing/multithreads (e.g. using `multiprocessing.Pool`, `concurrent.futures.ThreadPoolExecutor`, or other multiprocessing/multithreading libraries) and in these cases this internal random state negatively affects the simulations in two ways
  - the internal random state becomes a shared state that causes the different threads/processes blocked on each other.
  - the results of simulations become correlated.
  
  ---
  
  Suggested solution: Start to prefer passing `prng`s to methods/functions over maitaining an internal state. this `prng` should be an `np.random.Generator` instead of an `np.random.RandomState` so that we get a `spawn` method to use when starting threads/processes.
  
  related: https://github.com/quantumlib/Cirq/issues/6531