class_docstr: Distributed Centralized Lock
  
  Parameters
  ----------
  names
      Names of the locks to acquire. Choosing the same name allows two
      disconnected processes to coordinate a lock.
  client
      Client to use for communication with the scheduler.  If not given, the
      default global client will be used.
  
  Examples
  --------
  >>> lock = MultiLock(['x', 'y'])  # doctest: +SKIP
  >>> lock.acquire(timeout=1)  # doctest: +SKIP
  >>> # do things with protected resource 'x' and 'y'
  >>> lock.release()  # doctest: +SKIP
docstr: Release the lock if already acquired
docstr_tok: ["Release","the","lock","if","already","acquired"]
code: def release(self):
      """Release the lock if already acquired"""
      if not self.locked():
          raise ValueError("Lock is not yet acquired")
      ret = self.client.sync(
          self.client.scheduler.multi_lock_release, id=self.id
      )
      self._locked = False
      return ret
  
imports: from __future__ import annotations
  import asyncio
  import logging
  import uuid
  from collections import defaultdict
  from collections.abc import Hashable
  from dask.utils import parse_timedelta
  from distributed.client import Client
  from distributed.utils import TimeoutError, log_errors, wait_for
  from distributed.worker import get_worker
label_desc: Use image node_id 3 for calling the MultiLock obj's underlying member method code with example usage: obj.release() and returns: ret