mirror of
https://github.com/python/cpython.git
synced 2025-08-04 08:59:19 +00:00
Fix #8684: make sched.scheduler class thread-safe
This commit is contained in:
parent
a23d65ccfe
commit
73520d57eb
4 changed files with 45 additions and 54 deletions
|
@ -27,6 +27,9 @@ scheduler:
|
||||||
|
|
||||||
.. versionchanged:: 3.3
|
.. versionchanged:: 3.3
|
||||||
*timefunc* and *delayfunc* parameters are optional.
|
*timefunc* and *delayfunc* parameters are optional.
|
||||||
|
.. versionchanged:: 3.3
|
||||||
|
:class:`scheduler` class can be safely used in multi-threaded
|
||||||
|
environments.
|
||||||
|
|
||||||
Example::
|
Example::
|
||||||
|
|
||||||
|
@ -47,33 +50,6 @@ Example::
|
||||||
From print_time 930343700.273
|
From print_time 930343700.273
|
||||||
930343700.276
|
930343700.276
|
||||||
|
|
||||||
In multi-threaded environments, the :class:`scheduler` class has limitations
|
|
||||||
with respect to thread-safety, inability to insert a new task before
|
|
||||||
the one currently pending in a running scheduler, and holding up the main
|
|
||||||
thread until the event queue is empty. Instead, the preferred approach
|
|
||||||
is to use the :class:`threading.Timer` class instead.
|
|
||||||
|
|
||||||
Example::
|
|
||||||
|
|
||||||
>>> import time
|
|
||||||
>>> from threading import Timer
|
|
||||||
>>> def print_time():
|
|
||||||
... print("From print_time", time.time())
|
|
||||||
...
|
|
||||||
>>> def print_some_times():
|
|
||||||
... print(time.time())
|
|
||||||
... Timer(5, print_time, ()).start()
|
|
||||||
... Timer(10, print_time, ()).start()
|
|
||||||
... time.sleep(11) # sleep while time-delay events execute
|
|
||||||
... print(time.time())
|
|
||||||
...
|
|
||||||
>>> print_some_times()
|
|
||||||
930343690.257
|
|
||||||
From print_time 930343695.274
|
|
||||||
From print_time 930343700.273
|
|
||||||
930343701.301
|
|
||||||
|
|
||||||
|
|
||||||
.. _scheduler-objects:
|
.. _scheduler-objects:
|
||||||
|
|
||||||
Scheduler Objects
|
Scheduler Objects
|
||||||
|
|
|
@ -662,6 +662,10 @@ should be used. For example, this will send a ``'HEAD'`` request::
|
||||||
sched
|
sched
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
* :class:`~sched.scheduler` class can now be safely used in multi-threaded
|
||||||
|
environments. (Contributed by Josiah Carlson and Giampaolo Rodolà in
|
||||||
|
:issue:`8684`)
|
||||||
|
|
||||||
* *timefunc* and *delayfunct* parameters of :class:`~sched.scheduler` class
|
* *timefunc* and *delayfunct* parameters of :class:`~sched.scheduler` class
|
||||||
constructor are now optional and defaults to :func:`time.time` and
|
constructor are now optional and defaults to :func:`time.time` and
|
||||||
:func:`time.sleep` respectively. (Contributed by Chris Clark in
|
:func:`time.sleep` respectively. (Contributed by Chris Clark in
|
||||||
|
|
62
Lib/sched.py
62
Lib/sched.py
|
@ -30,6 +30,7 @@ has another way to reference private data (besides global variables).
|
||||||
|
|
||||||
import time
|
import time
|
||||||
import heapq
|
import heapq
|
||||||
|
import threading
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
__all__ = ["scheduler"]
|
__all__ = ["scheduler"]
|
||||||
|
@ -48,6 +49,7 @@ class scheduler:
|
||||||
"""Initialize a new instance, passing the time and delay
|
"""Initialize a new instance, passing the time and delay
|
||||||
functions"""
|
functions"""
|
||||||
self._queue = []
|
self._queue = []
|
||||||
|
self._lock = threading.RLock()
|
||||||
self.timefunc = timefunc
|
self.timefunc = timefunc
|
||||||
self.delayfunc = delayfunc
|
self.delayfunc = delayfunc
|
||||||
|
|
||||||
|
@ -58,9 +60,10 @@ class scheduler:
|
||||||
if necessary.
|
if necessary.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
event = Event(time, priority, action, argument, kwargs)
|
with self._lock:
|
||||||
heapq.heappush(self._queue, event)
|
event = Event(time, priority, action, argument, kwargs)
|
||||||
return event # The ID
|
heapq.heappush(self._queue, event)
|
||||||
|
return event # The ID
|
||||||
|
|
||||||
def enter(self, delay, priority, action, argument=[], kwargs={}):
|
def enter(self, delay, priority, action, argument=[], kwargs={}):
|
||||||
"""A variant that specifies the time as a relative time.
|
"""A variant that specifies the time as a relative time.
|
||||||
|
@ -68,8 +71,9 @@ class scheduler:
|
||||||
This is actually the more commonly used interface.
|
This is actually the more commonly used interface.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
time = self.timefunc() + delay
|
with self._lock:
|
||||||
return self.enterabs(time, priority, action, argument, kwargs)
|
time = self.timefunc() + delay
|
||||||
|
return self.enterabs(time, priority, action, argument, kwargs)
|
||||||
|
|
||||||
def cancel(self, event):
|
def cancel(self, event):
|
||||||
"""Remove an event from the queue.
|
"""Remove an event from the queue.
|
||||||
|
@ -78,12 +82,14 @@ class scheduler:
|
||||||
If the event is not in the queue, this raises ValueError.
|
If the event is not in the queue, this raises ValueError.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self._queue.remove(event)
|
with self._lock:
|
||||||
heapq.heapify(self._queue)
|
self._queue.remove(event)
|
||||||
|
heapq.heapify(self._queue)
|
||||||
|
|
||||||
def empty(self):
|
def empty(self):
|
||||||
"""Check whether the queue is empty."""
|
"""Check whether the queue is empty."""
|
||||||
return not self._queue
|
with self._lock:
|
||||||
|
return not self._queue
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""Execute events until the queue is empty.
|
"""Execute events until the queue is empty.
|
||||||
|
@ -108,24 +114,25 @@ class scheduler:
|
||||||
"""
|
"""
|
||||||
# localize variable access to minimize overhead
|
# localize variable access to minimize overhead
|
||||||
# and to improve thread safety
|
# and to improve thread safety
|
||||||
q = self._queue
|
with self._lock:
|
||||||
delayfunc = self.delayfunc
|
q = self._queue
|
||||||
timefunc = self.timefunc
|
delayfunc = self.delayfunc
|
||||||
pop = heapq.heappop
|
timefunc = self.timefunc
|
||||||
while q:
|
pop = heapq.heappop
|
||||||
time, priority, action, argument, kwargs = checked_event = q[0]
|
while q:
|
||||||
now = timefunc()
|
time, priority, action, argument, kwargs = checked_event = q[0]
|
||||||
if now < time:
|
now = timefunc()
|
||||||
delayfunc(time - now)
|
if now < time:
|
||||||
else:
|
delayfunc(time - now)
|
||||||
event = pop(q)
|
|
||||||
# Verify that the event was not removed or altered
|
|
||||||
# by another thread after we last looked at q[0].
|
|
||||||
if event is checked_event:
|
|
||||||
action(*argument, **kwargs)
|
|
||||||
delayfunc(0) # Let other threads run
|
|
||||||
else:
|
else:
|
||||||
heapq.heappush(q, event)
|
event = pop(q)
|
||||||
|
# Verify that the event was not removed or altered
|
||||||
|
# by another thread after we last looked at q[0].
|
||||||
|
if event is checked_event:
|
||||||
|
action(*argument, **kwargs)
|
||||||
|
delayfunc(0) # Let other threads run
|
||||||
|
else:
|
||||||
|
heapq.heappush(q, event)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def queue(self):
|
def queue(self):
|
||||||
|
@ -138,5 +145,6 @@ class scheduler:
|
||||||
# Use heapq to sort the queue rather than using 'sorted(self._queue)'.
|
# Use heapq to sort the queue rather than using 'sorted(self._queue)'.
|
||||||
# With heapq, two events scheduled at the same time will show in
|
# With heapq, two events scheduled at the same time will show in
|
||||||
# the actual order they would be retrieved.
|
# the actual order they would be retrieved.
|
||||||
events = self._queue[:]
|
with self._lock:
|
||||||
return map(heapq.heappop, [events]*len(events))
|
events = self._queue[:]
|
||||||
|
return map(heapq.heappop, [events]*len(events))
|
||||||
|
|
|
@ -409,6 +409,9 @@ Core and Builtins
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #8684 sched.scheduler class can be safely used in multi-threaded
|
||||||
|
environments.
|
||||||
|
|
||||||
- Alias resource.error to OSError ala PEP 3151.
|
- Alias resource.error to OSError ala PEP 3151.
|
||||||
|
|
||||||
- Issue #5689: Add support for lzma compression to the tarfile module.
|
- Issue #5689: Add support for lzma compression to the tarfile module.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue