code: def sync_memoize(f):
      
      # TODO: Think about an f that is recursive
      memory = {}
      lock = Lock()
  
      @wraps(f)
      def new_f(*args):
          try:
              return memory[args]
          except KeyError:
              # on cache misses, retry with lock held
              with lock:
                  try:
                      return memory[args]
                  except KeyError:
                      r = f(*args)
                      memory[args] = r
                      return r
      return new_f
docs: Like memoize, but guarantees that decorated function is only called once, even when multiple
      threads are calling the decorating function with multiple parameters.