'Return a sequence of cached values based on a sequence of keys.
The behavior for generation of values based on keys corresponds
to that of :meth:`.Region.get_or_create`, with the exception that
the ``creator()`` function may be asked to generate any subset of
the given keys.   The list of keys to be generated is passed to
``creator()``, and ``creator()`` should return the generated values
as a sequence corresponding to the order of the keys.
The method uses the same approach as :meth:`.Region.get_multi`
and :meth:`.Region.set_multi` to get and set values from the
backend.
:param keys: Sequence of keys to be retrieved.
:param creator: function which accepts a sequence of keys and
returns a sequence of new values.
:param expiration_time: optional expiration time which will overide
the expiration time already configured on this :class:`.CacheRegion`
if not None.   To set no expiration, use the value -1.
:param should_cache_fn: optional callable function which will receive
each value returned by the "creator", and will then return True or
False, indicating if the value should actually be cached or not.  If
it returns False, the value is still returned, but isn\'t cached.
.. versionadded:: 0.5.0
.. seealso::
:meth:`.CacheRegion.cache_multi_on_arguments`
:meth:`.CacheRegion.get_or_create`'

def get_or_create_multi(self, keys, creator, expiration_time=None, should_cache_fn=None):

    def get_value(key):
        value = values.get(key, NO_VALUE)
        if ((value is NO_VALUE) or (value.metadata['v'] != value_version) or (self._hard_invalidated and (value.metadata['ct'] < self._hard_invalidated))):
            return (value.payload, 0)
        else:
            ct = value.metadata['ct']
            if self._soft_invalidated:
                if (ct < self._soft_invalidated):
                    ct = ((time.time() - expiration_time) - 0.0001)
            return (value.payload, ct)
    def gen_value():
        raise NotImplementedError()
    def async_creator(key, mutex):
        mutexes[key] = mutex
    if (expiration_time is None):
        expiration_time = self.expiration_time
    if ((expiration_time is None) and self._soft_invalidated):
        raise exception.DogpileCacheException('Non-None    expiration    time    required    for    soft    invalidation')
    if (expiration_time == (-1)):
        expiration_time = None
    mutexes = {}
    sorted_unique_keys = sorted(set(keys))
    if self.key_mangler:
        mangled_keys = [self.key_mangler(k) for k in sorted_unique_keys]
    else:
        mangled_keys = sorted_unique_keys
    orig_to_mangled = dict(zip(sorted_unique_keys, mangled_keys))
    values = dict(zip(mangled_keys, self.backend.get_multi(mangled_keys)))
    for (orig_key, mangled_key) in orig_to_mangled.items():
        with Lock(self._mutex(mangled_key), gen_value, (lambda : get_value(mangled_key)), expiration_time, async_creator=(lambda mutex: async_creator(orig_key, mutex))):
            pass
    try:
        if mutexes:
            keys_to_get = sorted(mutexes)
            new_values = creator(*keys_to_get)
            values_w_created = dict(((orig_to_mangled[k], self._value(v)) for (k, v) in zip(keys_to_get, new_values)))
            if (not should_cache_fn):
                self.backend.set_multi(values_w_created)
            else:
                self.backend.set_multi(dict(((k, v) for (k, v) in values_w_created.items() if should_cache_fn(v[0]))))
            values.update(values_w_created)
        return [values[orig_to_mangled[k]].payload for k in keys]
    finally:
        for mutex in mutexes.values():
            mutex.release()
