Is there a way to implement a lock in Python for multithreading purposes whose acquire
method can have an arbitrary timeout? The only working solutions I found
Okay, this is already implemented in python 3.2 or above: https://docs.python.org/3/library/threading.html Look for threading.TIMEOUT_MAX
But I improved on the test case over frans' version ... though this is already a waste of time if you're on py3.2 or above:
from unittest.mock import patch, Mock
import unittest
import os
import sys
import logging
import traceback
import threading
import time
from Util import ThreadingUtil
class ThreadingUtilTests(unittest.TestCase):
def setUp(self):
pass
def tearDown(self):
pass
# https://www.pythoncentral.io/pythons-time-sleep-pause-wait-sleep-stop-your-code/
def testTimeoutLock(self):
faulted = [False, False, False]
def locking_thread_fn(threadId, lock, duration, timeout):
try:
threadName = "Thread#" + str(threadId)
with ThreadingUtil.TimeoutLock(threadName, lock, timeout=timeout, raise_on_timeout=True):
print('%x: "%s" begins to work..' % (threading.get_ident(), threadName))
time.sleep(duration)
print('%x: "%s" finished' % (threading.get_ident(), threadName))
except:
faulted[threadId] = True
_lock = ThreadingUtil.TimeoutLock.lock()
_sleepDuration = [5, 10, 1]
_threads = []
for i in range(3):
_duration = _sleepDuration[i]
_timeout = 6
print("Wait duration (sec): " + str(_duration) + ", Timeout (sec): " + str(_timeout))
_worker = threading.Thread(
target=locking_thread_fn,
args=(i, _lock, _duration, _timeout)
)
_threads.append(_worker)
for t in _threads: t.start()
for t in _threads: t.join()
self.assertEqual(faulted[0], False)
self.assertEqual(faulted[1], False)
self.assertEqual(faulted[2], True)
Now under "Util" folder, I have "ThreadingUtil.py":
import time
import threading
# https://stackoverflow.com/questions/8392640/how-to-implement-a-lock-with-a-timeout-in-python-2-7
# https://docs.python.org/3.4/library/asyncio-sync.html#asyncio.Condition
# https://stackoverflow.com/questions/28664720/how-to-create-global-lock-semaphore-with-multiprocessing-pool-in-python
# https://hackernoon.com/synchronization-primitives-in-python-564f89fee732
class TimeoutLock(object):
''' taken from https://stackoverflow.com/a/8393033/1668622
'''
class lock:
def __init__(self):
self.owner = None
self.lock = threading.Lock()
self.cond = threading.Condition()
def _release(self):
self.owner = None
self.lock.release()
with self.cond:
self.cond.notify()
def __init__(self, owner, lock, timeout=1, raise_on_timeout=False):
self._owner = owner
self._lock = lock
self._timeout = timeout
self._raise_on_timeout = raise_on_timeout
# http://effbot.org/zone/python-with-statement.htm
def __enter__(self):
self.acquire()
return self
def __exit__(self, type, value, tb):
''' will only be called if __enter__ did not raise '''
self.release()
def acquire(self):
if self._raise_on_timeout:
if not self._waitLock():
raise RuntimeError('"%s" could not aquire lock within %d sec'
% (self._owner, self._timeout))
else:
while True:
if self._waitLock():
break
print('"%s" is waiting for "%s" and is getting bored...'
% (self._owner, self._lock.owner))
self._lock.owner = self._owner
def release(self):
self._lock._release()
def _waitLock(self):
with self._lock.cond:
_current_t = _start_t = time.time()
while _current_t < _start_t + self._timeout:
if self._lock.lock.acquire(False):
return True
else:
self._lock.cond.wait(self._timeout - _current_t + _start_t)
_current_t = time.time()
return False