Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gevent
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
gevent
Commits
b4db40b8
Commit
b4db40b8
authored
Mar 24, 2018
by
Jason Madden
Committed by
GitHub
Mar 24, 2018
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1153 from gevent/threadpool-opts
Optimizations for threadpool
parents
277e34ca
c21db37f
Changes
8
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
336 additions
and
143 deletions
+336
-143
CHANGES.rst
CHANGES.rst
+3
-0
benchmarks/bench_pool.py
benchmarks/bench_pool.py
+16
-0
benchmarks/bench_threadpool.py
benchmarks/bench_threadpool.py
+124
-0
src/gevent/_threading.py
src/gevent/_threading.py
+27
-36
src/gevent/pool.py
src/gevent/pool.py
+126
-79
src/gevent/queue.py
src/gevent/queue.py
+1
-1
src/gevent/threadpool.py
src/gevent/threadpool.py
+36
-26
src/greentest/test__pool.py
src/greentest/test__pool.py
+3
-1
No files found.
CHANGES.rst
View file @
b4db40b8
...
@@ -42,6 +42,9 @@ Enhancements
...
@@ -42,6 +42,9 @@ Enhancements
- Hub objects now include the value of their ``name`` attribute in
- Hub objects now include the value of their ``name`` attribute in
their repr.
their repr.
- Pools for greenlets and threads have lower overhead, especially for
``map``. See :pr:`1153`.
Monitoring and Debugging
Monitoring and Debugging
------------------------
------------------------
...
...
benchmarks/bench_pool.py
0 → 100644
View file @
b4db40b8
# -*- coding: utf-8 -*-
"""
Benchmarks for greenlet pool.
"""
from
__future__
import
absolute_import
from
__future__
import
division
from
__future__
import
print_function
import
gevent.pool
import
bench_threadpool
bench_threadpool
.
ThreadPool
=
gevent
.
pool
.
Pool
if
__name__
==
'__main__'
:
bench_threadpool
.
main
()
benchmarks/bench_threadpool.py
0 → 100644
View file @
b4db40b8
# -*- coding: utf-8 -*-
"""
Benchmarks for thread pool.
"""
from
__future__
import
absolute_import
from
__future__
import
division
from
__future__
import
print_function
import
perf
from
gevent.threadpool
import
ThreadPool
try
:
xrange
=
xrange
except
NameError
:
xrange
=
range
def
noop
():
"Does nothing"
def
identity
(
i
):
return
i
PAR_COUNT
=
5
N
=
20
def
bench_apply
(
loops
):
pool
=
ThreadPool
(
1
)
t0
=
perf
.
perf_counter
()
for
_
in
xrange
(
loops
):
for
_
in
xrange
(
N
):
pool
.
apply
(
noop
)
pool
.
join
()
pool
.
kill
()
return
perf
.
perf_counter
()
-
t0
def
bench_spawn_wait
(
loops
):
pool
=
ThreadPool
(
1
)
t0
=
perf
.
perf_counter
()
for
_
in
xrange
(
loops
):
for
_
in
xrange
(
N
):
r
=
pool
.
spawn
(
noop
)
r
.
get
()
pool
.
join
()
pool
.
kill
()
return
perf
.
perf_counter
()
-
t0
def
_map
(
pool
,
pool_func
,
loops
):
data
=
[
1
]
*
N
t0
=
perf
.
perf_counter
()
# Must collect for imap to finish
for
_
in
xrange
(
loops
):
list
(
pool_func
(
identity
,
data
))
pool
.
join
()
pool
.
kill
()
return
perf
.
perf_counter
()
-
t0
def
_ppool
():
pool
=
ThreadPool
(
PAR_COUNT
)
pool
.
size
=
PAR_COUNT
return
pool
def
bench_map_seq
(
loops
):
pool
=
ThreadPool
(
1
)
return
_map
(
pool
,
pool
.
map
,
loops
)
def
bench_map_par
(
loops
):
pool
=
_ppool
()
return
_map
(
pool
,
pool
.
map
,
loops
)
def
bench_imap_seq
(
loops
):
pool
=
ThreadPool
(
1
)
return
_map
(
pool
,
pool
.
imap
,
loops
)
def
bench_imap_par
(
loops
):
pool
=
_ppool
()
return
_map
(
pool
,
pool
.
imap
,
loops
)
def
bench_imap_un_seq
(
loops
):
pool
=
ThreadPool
(
1
)
return
_map
(
pool
,
pool
.
imap_unordered
,
loops
)
def
bench_imap_un_par
(
loops
):
pool
=
_ppool
()
return
_map
(
pool
,
pool
.
imap_unordered
,
loops
)
def
main
():
runner
=
perf
.
Runner
()
runner
.
bench_time_func
(
'imap_unordered_seq'
,
bench_imap_un_seq
)
runner
.
bench_time_func
(
'imap_unordered_par'
,
bench_imap_un_par
)
runner
.
bench_time_func
(
'imap_seq'
,
bench_imap_seq
)
runner
.
bench_time_func
(
'imap_par'
,
bench_imap_par
)
runner
.
bench_time_func
(
'map_seq'
,
bench_map_seq
)
runner
.
bench_time_func
(
'map_par'
,
bench_map_par
)
runner
.
bench_time_func
(
'apply'
,
bench_apply
)
runner
.
bench_time_func
(
'spawn'
,
bench_spawn_wait
)
if
__name__
==
'__main__'
:
main
()
src/gevent/_threading.py
View file @
b4db40b8
...
@@ -8,14 +8,12 @@ This module is missing 'Thread' class, but includes 'Queue'.
...
@@ -8,14 +8,12 @@ This module is missing 'Thread' class, but includes 'Queue'.
from
__future__
import
absolute_import
from
__future__
import
absolute_import
from
collections
import
deque
from
collections
import
deque
from
itertools
import
islice
as
_islice
from
gevent
import
monkey
from
gevent
import
monkey
from
gevent._compat
import
thread_mod_name
from
gevent._compat
import
thread_mod_name
__all__
=
[
__all__
=
[
'Condition'
,
'Lock'
,
'Lock'
,
'Queue'
,
'Queue'
,
]
]
...
@@ -26,14 +24,13 @@ start_new_thread, Lock, get_thread_ident, = monkey.get_original(thread_mod_name,
...
@@ -26,14 +24,13 @@ start_new_thread, Lock, get_thread_ident, = monkey.get_original(thread_mod_name,
])
])
class
Condition
(
object
):
class
_
Condition
(
object
):
# pylint:disable=method-hidden
# pylint:disable=method-hidden
def
__init__
(
self
,
lock
):
def
__init__
(
self
,
lock
):
self
.
__lock
=
lock
self
.
__lock
=
lock
# Export the lock's acquire() and release() methods
self
.
__waiters
=
[]
self
.
acquire
=
lock
.
acquire
self
.
release
=
lock
.
release
# If the lock defines _release_save() and/or _acquire_restore(),
# If the lock defines _release_save() and/or _acquire_restore(),
# these override the default implementations (which just call
# these override the default implementations (which just call
# release() and acquire() on the lock). Ditto for _is_owned().
# release() and acquire() on the lock). Ditto for _is_owned().
...
@@ -49,13 +46,12 @@ class Condition(object):
...
@@ -49,13 +46,12 @@ class Condition(object):
self
.
_is_owned
=
lock
.
_is_owned
self
.
_is_owned
=
lock
.
_is_owned
except
AttributeError
:
except
AttributeError
:
pass
pass
self
.
__waiters
=
[]
def
__enter__
(
self
):
def
__enter__
(
self
):
return
self
.
__lock
.
__enter__
()
return
self
.
__lock
.
__enter__
()
def
__exit__
(
self
,
*
args
):
def
__exit__
(
self
,
t
,
v
,
tb
):
return
self
.
__lock
.
__exit__
(
*
args
)
return
self
.
__lock
.
__exit__
(
t
,
v
,
tb
)
def
__repr__
(
self
):
def
__repr__
(
self
):
return
"<Condition(%s, %d)>"
%
(
self
.
__lock
,
len
(
self
.
__waiters
))
return
"<Condition(%s, %d)>"
%
(
self
.
__lock
,
len
(
self
.
__waiters
))
...
@@ -75,8 +71,7 @@ class Condition(object):
...
@@ -75,8 +71,7 @@ class Condition(object):
return
True
return
True
def
wait
(
self
):
def
wait
(
self
):
if
not
self
.
_is_owned
():
# The condition MUST be owned, but we don't check that.
raise
RuntimeError
(
"cannot wait on un-acquired lock"
)
waiter
=
Lock
()
waiter
=
Lock
()
waiter
.
acquire
()
waiter
.
acquire
()
self
.
__waiters
.
append
(
waiter
)
self
.
__waiters
.
append
(
waiter
)
...
@@ -86,19 +81,15 @@ class Condition(object):
...
@@ -86,19 +81,15 @@ class Condition(object):
finally
:
finally
:
self
.
_acquire_restore
(
saved_state
)
self
.
_acquire_restore
(
saved_state
)
def
notify
(
self
,
n
=
1
):
def
notify_one
(
self
):
if
not
self
.
_is_owned
():
# The condition MUST be owned, but we don't check that.
raise
RuntimeError
(
"cannot notify on un-acquired lock"
)
all_waiters
=
self
.
__waiters
waiters_to_notify
=
deque
(
_islice
(
all_waiters
,
n
))
if
not
waiters_to_notify
:
return
for
waiter
in
waiters_to_notify
:
waiter
.
release
()
try
:
try
:
all_waiters
.
remove
(
waiter
)
waiter
=
self
.
__waiters
.
pop
()
except
ValueError
:
except
IndexError
:
# Nobody around
pass
pass
else
:
waiter
.
release
()
class
Queue
(
object
):
class
Queue
(
object
):
...
@@ -107,17 +98,18 @@ class Queue(object):
...
@@ -107,17 +98,18 @@ class Queue(object):
The queue is always infinite size.
The queue is always infinite size.
"""
"""
__slots__
=
(
'_queue'
,
'_mutex'
,
'_not_empty'
,
'unfinished_tasks'
)
def
__init__
(
self
):
def
__init__
(
self
):
self
.
queue
=
deque
()
self
.
_
queue
=
deque
()
# mutex must be held whenever the queue is mutating. All methods
# mutex must be held whenever the queue is mutating. All methods
# that acquire mutex must release it before returning. mutex
# that acquire mutex must release it before returning. mutex
# is shared between the three conditions, so acquiring and
# is shared between the three conditions, so acquiring and
# releasing the conditions also acquires and releases mutex.
# releasing the conditions also acquires and releases mutex.
self
.
mutex
=
Lock
()
self
.
_
mutex
=
Lock
()
# Notify not_empty whenever an item is added to the queue; a
# Notify not_empty whenever an item is added to the queue; a
# thread waiting to get is notified then.
# thread waiting to get is notified then.
self
.
not_empty
=
Condition
(
self
.
mutex
)
self
.
_not_empty
=
_Condition
(
self
.
_
mutex
)
self
.
unfinished_tasks
=
0
self
.
unfinished_tasks
=
0
...
@@ -135,7 +127,7 @@ class Queue(object):
...
@@ -135,7 +127,7 @@ class Queue(object):
Raises a ValueError if called more times than there were items
Raises a ValueError if called more times than there were items
placed in the queue.
placed in the queue.
"""
"""
with
self
.
mutex
:
with
self
.
_
mutex
:
unfinished
=
self
.
unfinished_tasks
-
1
unfinished
=
self
.
unfinished_tasks
-
1
if
unfinished
<=
0
:
if
unfinished
<=
0
:
if
unfinished
<
0
:
if
unfinished
<
0
:
...
@@ -144,8 +136,7 @@ class Queue(object):
...
@@ -144,8 +136,7 @@ class Queue(object):
def
qsize
(
self
,
len
=
len
):
def
qsize
(
self
,
len
=
len
):
"""Return the approximate size of the queue (not reliable!)."""
"""Return the approximate size of the queue (not reliable!)."""
with
self
.
mutex
:
return
len
(
self
.
_queue
)
return
len
(
self
.
queue
)
def
empty
(
self
):
def
empty
(
self
):
"""Return True if the queue is empty, False otherwise (not reliable!)."""
"""Return True if the queue is empty, False otherwise (not reliable!)."""
...
@@ -158,16 +149,16 @@ class Queue(object):
...
@@ -158,16 +149,16 @@ class Queue(object):
def
put
(
self
,
item
):
def
put
(
self
,
item
):
"""Put an item into the queue.
"""Put an item into the queue.
"""
"""
with
self
.
mutex
:
with
self
.
_not_empty
:
self
.
queue
.
append
(
item
)
self
.
_
queue
.
append
(
item
)
self
.
unfinished_tasks
+=
1
self
.
unfinished_tasks
+=
1
self
.
not_empty
.
notify
()
self
.
_not_empty
.
notify_one
()
def
get
(
self
):
def
get
(
self
):
"""Remove and return an item from the queue.
"""Remove and return an item from the queue.
"""
"""
with
self
.
mutex
:
with
self
.
_not_empty
:
while
not
self
.
queue
:
while
not
self
.
_
queue
:
self
.
not_empty
.
wait
()
self
.
_
not_empty
.
wait
()
item
=
self
.
queue
.
popleft
()
item
=
self
.
_
queue
.
popleft
()
return
item
return
item
src/gevent/pool.py
View file @
b4db40b8
...
@@ -13,8 +13,8 @@ provides a way to limit concurrency: its :meth:`spawn <Pool.spawn>`
...
@@ -13,8 +13,8 @@ provides a way to limit concurrency: its :meth:`spawn <Pool.spawn>`
method blocks if the number of greenlets in the pool has already
method blocks if the number of greenlets in the pool has already
reached the limit, until there is a free slot.
reached the limit, until there is a free slot.
"""
"""
from
__future__
import
print_function
,
absolute_import
,
division
from
bisect
import
insort_right
try
:
try
:
from
itertools
import
izip
from
itertools
import
izip
except
ImportError
:
except
ImportError
:
...
@@ -28,7 +28,11 @@ from gevent.timeout import Timeout
...
@@ -28,7 +28,11 @@ from gevent.timeout import Timeout
from
gevent.event
import
Event
from
gevent.event
import
Event
from
gevent.lock
import
Semaphore
,
DummySemaphore
from
gevent.lock
import
Semaphore
,
DummySemaphore
__all__
=
[
'Group'
,
'Pool'
,
'PoolFull'
]
__all__
=
[
'Group'
,
'Pool'
,
'PoolFull'
,
]
class
IMapUnordered
(
Greenlet
):
class
IMapUnordered
(
Greenlet
):
...
@@ -36,12 +40,11 @@ class IMapUnordered(Greenlet):
...
@@ -36,12 +40,11 @@ class IMapUnordered(Greenlet):
At iterator of map results.
At iterator of map results.
"""
"""
_zipped
=
False
def
__init__
(
self
,
func
,
iterable
,
spawn
=
None
,
maxsize
=
None
,
_zipped
=
False
):
def
__init__
(
self
,
func
,
iterable
,
spawn
=
None
,
maxsize
=
None
,
_zipped
=
False
):
"""
"""
An iterator that.
An iterator that.
:keyword callable spawn: The function we use to
:keyword int maxsize: If given and not-None, specifies the maximum number of
:keyword int maxsize: If given and not-None, specifies the maximum number of
finished results that will be allowed to accumulated awaiting the reader;
finished results that will be allowed to accumulated awaiting the reader;
more than that number of results will cause map function greenlets to begin
more than that number of results will cause map function greenlets to begin
...
@@ -56,7 +59,6 @@ class IMapUnordered(Greenlet):
...
@@ -56,7 +59,6 @@ class IMapUnordered(Greenlet):
Greenlet
.
__init__
(
self
)
Greenlet
.
__init__
(
self
)
if
spawn
is
not
None
:
if
spawn
is
not
None
:
self
.
spawn
=
spawn
self
.
spawn
=
spawn
if
_zipped
:
self
.
_zipped
=
_zipped
self
.
_zipped
=
_zipped
self
.
func
=
func
self
.
func
=
func
self
.
iterable
=
iterable
self
.
iterable
=
iterable
...
@@ -82,19 +84,14 @@ class IMapUnordered(Greenlet):
...
@@ -82,19 +84,14 @@ class IMapUnordered(Greenlet):
factory
=
DummySemaphore
factory
=
DummySemaphore
self
.
_result_semaphore
=
factory
(
maxsize
)
self
.
_result_semaphore
=
factory
(
maxsize
)
self
.
count
=
0
self
.
_outstanding_tasks
=
0
# The index (zero based) of the maximum number of
# results we will have.
self
.
_max_index
=
-
1
self
.
finished
=
False
self
.
finished
=
False
# If the queue size is unbounded, then we want to call all
# the links (_on_finish and _on_result) directly in the hub greenlet
# for efficiency. However, if the queue is bounded, we can't do that if
# the queue might block (because if there's no waiter the hub can switch to,
# the queue simply raises Full). Therefore, in that case, we use
# the safer, somewhat-slower (because it spawns a greenlet) link() methods.
# This means that _on_finish and _on_result can be called and interleaved in any order
# if the call to self.queue.put() blocks..
# Note that right now we're not bounding the queue, instead using a semaphore.
self
.
rawlink
(
self
.
_on_finish
)
# We're iterating in a different greenlet than we're running.
def
__iter__
(
self
):
def
__iter__
(
self
):
return
self
return
self
...
@@ -109,10 +106,11 @@ class IMapUnordered(Greenlet):
...
@@ -109,10 +106,11 @@ class IMapUnordered(Greenlet):
def
_inext
(
self
):
def
_inext
(
self
):
return
self
.
queue
.
get
()
return
self
.
queue
.
get
()
def
_ispawn
(
self
,
func
,
item
):
def
_ispawn
(
self
,
func
,
item
,
item_index
):
self
.
_result_semaphore
.
acquire
()
self
.
_result_semaphore
.
acquire
()
self
.
count
+=
1
self
.
_outstanding_tasks
+=
1
g
=
self
.
spawn
(
func
,
item
)
if
not
self
.
_zipped
else
self
.
spawn
(
func
,
*
item
)
g
=
self
.
spawn
(
func
,
item
)
if
not
self
.
_zipped
else
self
.
spawn
(
func
,
*
item
)
g
.
_imap_task_index
=
item_index
g
.
rawlink
(
self
.
_on_result
)
g
.
rawlink
(
self
.
_on_result
)
return
g
return
g
...
@@ -120,23 +118,21 @@ class IMapUnordered(Greenlet):
...
@@ -120,23 +118,21 @@ class IMapUnordered(Greenlet):
try
:
try
:
func
=
self
.
func
func
=
self
.
func
for
item
in
self
.
iterable
:
for
item
in
self
.
iterable
:
self
.
_ispawn
(
func
,
item
)
self
.
_max_index
+=
1
self
.
_ispawn
(
func
,
item
,
self
.
_max_index
)
self
.
_on_finish
(
None
)
except
BaseException
as
e
:
self
.
_on_finish
(
e
)
raise
finally
:
finally
:
self
.
__dict__
.
pop
(
'spawn'
,
None
)
self
.
__dict__
.
pop
(
'spawn'
,
None
)
self
.
__dict__
.
pop
(
'func'
,
None
)
self
.
__dict__
.
pop
(
'func'
,
None
)
self
.
__dict__
.
pop
(
'iterable'
,
None
)
self
.
__dict__
.
pop
(
'iterable'
,
None
)
def
_on_result
(
self
,
greenlet
):
def
_on_result
(
self
,
greenlet
):
# This method can either be called in the hub greenlet (if the
# This method will be called in the hub greenlet (we rawlink)
# queue is unbounded) or its own greenlet. If it's called in
self
.
_outstanding_tasks
-=
1
# its own greenlet, the calls to put() may block and switch
count
=
self
.
_outstanding_tasks
# greenlets, which in turn could mutate our state. So any
# state on this object that we need to look at, notably
# self.count, we need to capture or mutate *before* we put.
# (Note that right now we're not bounding the queue, but we may
# choose to do so in the future so this implementation will be left in case.)
self
.
count
-=
1
count
=
self
.
count
finished
=
self
.
finished
finished
=
self
.
finished
ready
=
self
.
ready
()
ready
=
self
.
ready
()
put_finished
=
False
put_finished
=
False
...
@@ -151,20 +147,21 @@ class IMapUnordered(Greenlet):
...
@@ -151,20 +147,21 @@ class IMapUnordered(Greenlet):
self
.
queue
.
put
(
self
.
_iqueue_value_for_failure
(
greenlet
))
self
.
queue
.
put
(
self
.
_iqueue_value_for_failure
(
greenlet
))
if
put_finished
:
if
put_finished
:
self
.
queue
.
put
(
self
.
_iqueue_value_for_finished
())
self
.
queue
.
put
(
self
.
_iqueue_value_for_
self_
finished
())
def
_on_finish
(
self
,
_self
):
def
_on_finish
(
self
,
exception
):
# Called in this greenlet.
if
self
.
finished
:
if
self
.
finished
:
return
return
if
not
self
.
successful
()
:
if
exception
is
not
None
:
self
.
finished
=
True
self
.
finished
=
True
self
.
queue
.
put
(
self
.
_iqueue_value_for_self_failure
())
self
.
queue
.
put
(
self
.
_iqueue_value_for_self_failure
(
exception
))
return
return
if
self
.
count
<=
0
:
if
self
.
_outstanding_tasks
<=
0
:
self
.
finished
=
True
self
.
finished
=
True
self
.
queue
.
put
(
self
.
_iqueue_value_for_finished
())
self
.
queue
.
put
(
self
.
_iqueue_value_for_
self_
finished
())
def
_iqueue_value_for_success
(
self
,
greenlet
):
def
_iqueue_value_for_success
(
self
,
greenlet
):
return
greenlet
.
value
return
greenlet
.
value
...
@@ -172,75 +169,91 @@ class IMapUnordered(Greenlet):
...
@@ -172,75 +169,91 @@ class IMapUnordered(Greenlet):
def
_iqueue_value_for_failure
(
self
,
greenlet
):
def
_iqueue_value_for_failure
(
self
,
greenlet
):
return
Failure
(
greenlet
.
exception
,
getattr
(
greenlet
,
'_raise_exception'
))
return
Failure
(
greenlet
.
exception
,
getattr
(
greenlet
,
'_raise_exception'
))
def
_iqueue_value_for_finished
(
self
):
def
_iqueue_value_for_
self_
finished
(
self
):
return
Failure
(
StopIteration
)
return
Failure
(
StopIteration
)
def
_iqueue_value_for_self_failure
(
self
):
def
_iqueue_value_for_self_failure
(
self
,
exception
):
return
Failure
(
self
.
exception
,
self
.
_raise_exception
)
return
Failure
(
exception
,
self
.
_raise_exception
)
class
IMap
(
IMapUnordered
):
class
IMap
(
IMapUnordered
):
# A specialization of IMapUnordered that returns items
# A specialization of IMapUnordered that returns items
# in the order in which they were generated, not
# in the order in which they were generated, not
# the order in which they finish.
# the order in which they finish.
# We do this by storing tuples (order, value) in the queue
# not just value.
def
__init__
(
self
,
*
args
,
**
kwargs
):
def
__init__
(
self
,
*
args
,
**
kwargs
):
self
.
waiting
=
[]
# QQQ maybe deque will work faster there?
# The result dictionary: {index: value}
self
.
_results
=
{}
# The index of the result to return next.
self
.
index
=
0
self
.
index
=
0
self
.
maxindex
=
-
1
IMapUnordered
.
__init__
(
self
,
*
args
,
**
kwargs
)
IMapUnordered
.
__init__
(
self
,
*
args
,
**
kwargs
)
def
_inext
(
self
):
def
_inext
(
self
):
while
True
:
try
:
if
self
.
waiting
and
self
.
waiting
[
0
][
0
]
<=
self
.
index
:
value
=
self
.
_results
.
pop
(
self
.
index
)
_
,
value
=
self
.
waiting
.
pop
(
0
)
except
KeyError
:
else
:
# Wait for our index to finish.
while
1
:
index
,
value
=
self
.
queue
.
get
()
index
,
value
=
self
.
queue
.
get
()
if
index
>
self
.
index
:
if
index
==
self
.
index
:
insort_right
(
self
.
waiting
,
(
index
,
value
))
break
continue
else
:
self
.
_results
[
index
]
=
value
self
.
index
+=
1
self
.
index
+=
1
return
value
return
value
def
_ispawn
(
self
,
func
,
item
):
g
=
IMapUnordered
.
_ispawn
(
self
,
func
,
item
)
self
.
maxindex
+=
1
g
.
index
=
self
.
maxindex
return
g
def
_iqueue_value_for_success
(
self
,
greenlet
):
def
_iqueue_value_for_success
(
self
,
greenlet
):
return
(
greenlet
.
index
,
IMapUnordered
.
_iqueue_value_for_success
(
self
,
greenlet
))
return
(
greenlet
.
_imap_task_
index
,
IMapUnordered
.
_iqueue_value_for_success
(
self
,
greenlet
))
def
_iqueue_value_for_failure
(
self
,
greenlet
):
def
_iqueue_value_for_failure
(
self
,
greenlet
):
return
(
greenlet
.
index
,
IMapUnordered
.
_iqueue_value_for_failure
(
self
,
greenlet
))
return
(
greenlet
.
_imap_task_
index
,
IMapUnordered
.
_iqueue_value_for_failure
(
self
,
greenlet
))
def
_iqueue_value_for_finished
(
self
):
def
_iqueue_value_for_self_finished
(
self
):
self
.
maxindex
+=
1
return
(
self
.
_max_index
+
1
,
IMapUnordered
.
_iqueue_value_for_self_finished
(
self
))
return
(
self
.
maxindex
,
IMapUnordered
.
_iqueue_value_for_finished
(
self
))
def
_iqueue_value_for_self_failure
(
self
):
def
_iqueue_value_for_self_failure
(
self
,
exception
):
self
.
maxindex
+=
1
return
(
self
.
_max_index
+
1
,
IMapUnordered
.
_iqueue_value_for_self_failure
(
self
,
exception
))
return
(
self
.
maxindex
,
IMapUnordered
.
_iqueue_value_for_self_failure
(
self
))
class
GroupMappingMixin
(
object
):
class
GroupMappingMixin
(
object
):
# Internal, non-public API class.
# Internal, non-public API class.
# Provides mixin methods for implementing mapping pools. Subclasses must define:
# Provides mixin methods for implementing mapping pools. Subclasses must define:
# - self.spawn(func, *args, **kwargs): a function that runs `func` with `args`
def
spawn
(
self
,
func
,
*
args
,
**
kwargs
):
# and `awargs`, potentially asynchronously. Return a value with a `get` method that
"""
# blocks until the results of func are available, and a `link` method.
A function that runs *func* with *args* and *kwargs*, potentially
asynchronously. Return a value with a ``get`` method that blocks
until the results of func are available, and a ``rawlink`` method
that calls a callback when the results are available.
# - self._apply_immediately(): should the function passed to apply be called immediately,
If this object has an upper bound on how many asyncronously executing
# synchronously?
tasks can exist, this method may block until a slot becomes available.
"""
raise
NotImplementedError
()
# - self._apply_async_use_greenlet(): Should apply_async directly call
def
_apply_immediately
(
self
):
# Greenlet.spawn(), bypassing self.spawn? Return true when self.spawn would block
"""
should the function passed to apply be called immediately,
synchronously?
"""
raise
NotImplementedError
()
# - self._apply_async_cb_spawn(callback, result): Run the given callback function, possiblly
def
_apply_async_use_greenlet
(
self
):
# asynchronously, possibly synchronously.
"""
Should apply_async directly call Greenlet.spawn(), bypassing
`spawn`?
Return true when self.spawn would block.
"""
raise
NotImplementedError
()
def
_apply_async_cb_spawn
(
self
,
callback
,
result
):
"""
Run the given callback function, possibly
asynchronously, possibly synchronously.
"""
raise
NotImplementedError
()
def
apply_cb
(
self
,
func
,
args
=
None
,
kwds
=
None
,
callback
=
None
):
def
apply_cb
(
self
,
func
,
args
=
None
,
kwds
=
None
,
callback
=
None
):
"""
"""
...
@@ -325,13 +338,46 @@ class GroupMappingMixin(object):
...
@@ -325,13 +338,46 @@ class GroupMappingMixin(object):
return
func
(
*
args
,
**
kwds
)
return
func
(
*
args
,
**
kwds
)
return
self
.
spawn
(
func
,
*
args
,
**
kwds
).
get
()
return
self
.
spawn
(
func
,
*
args
,
**
kwds
).
get
()
def
__map
(
self
,
func
,
iterable
):
return
[
g
.
get
()
for
g
in
[
self
.
spawn
(
func
,
i
)
for
i
in
iterable
]]
def
map
(
self
,
func
,
iterable
):
def
map
(
self
,
func
,
iterable
):
"""Return a list made by applying the *func* to each element of
"""Return a list made by applying the *func* to each element of
the iterable.
the iterable.
.. seealso:: :meth:`imap`
.. seealso:: :meth:`imap`
"""
"""
return
list
(
self
.
imap
(
func
,
iterable
))
# We can't return until they're all done and in order. It
# wouldn't seem to much matter what order we wait on them in,
# so the simple, fast (50% faster than imap) solution would be:
# return [g.get() for g in
# [self.spawn(func, i) for i in iterable]]
# If the pool size is unlimited (or more than the len(iterable)), this
# is equivalent to imap (spawn() will never block, all of them run concurrently,
# we call get() in the order the iterable was given).
# Now lets imagine the pool if is limited size. Suppose the
# func is time.sleep, our pool is limited to 3 threads, and
# our input is [10, 1, 10, 1, 1] We would start three threads,
# one to sleep for 10, one to sleep for 1, and the last to
# sleep for 10. We would block starting the fourth thread. At
# time 1, we would finish the second thread and start another
# one for time 1. At time 2, we would finish that one and
# start the last thread, and then begin executing get() on the first
# thread.
# Because it's spawn that blocks, this is *also* equivalent to what
# imap would do.
# The one remaining difference is that imap runs in its own
# greenlet, potentially changing the way the event loop runs.
# That's easy enough to do.
g
=
Greenlet
.
spawn
(
self
.
__map
,
func
,
iterable
)
return
g
.
get
()
def
map_cb
(
self
,
func
,
iterable
,
callback
=
None
):
def
map_cb
(
self
,
func
,
iterable
,
callback
=
None
):
result
=
self
.
map
(
func
,
iterable
)
result
=
self
.
map
(
func
,
iterable
)
...
@@ -503,12 +549,12 @@ class Group(GroupMappingMixin):
...
@@ -503,12 +549,12 @@ class Group(GroupMappingMixin):
def
start
(
self
,
greenlet
):
def
start
(
self
,
greenlet
):
"""
"""
Add the **unstarted** *greenlet* to the collection of greenlets
Add the **unstarted** *greenlet* to the collection of greenlets
this group is monitoring, nd then start it.
this group is monitoring,
a
nd then start it.
"""
"""
self
.
add
(
greenlet
)
self
.
add
(
greenlet
)
greenlet
.
start
()
greenlet
.
start
()
def
spawn
(
self
,
*
args
,
**
kwargs
):
def
spawn
(
self
,
*
args
,
**
kwargs
):
# pylint:disable=arguments-differ
"""
"""
Begin a new greenlet with the given arguments (which are passed
Begin a new greenlet with the given arguments (which are passed
to the greenlet constructor) and add it to the collection of greenlets
to the greenlet constructor) and add it to the collection of greenlets
...
@@ -746,7 +792,8 @@ class Pool(Group):
...
@@ -746,7 +792,8 @@ class Pool(Group):
until space is available.
until space is available.
Usually you should call :meth:`start` to track and start the greenlet
Usually you should call :meth:`start` to track and start the greenlet
instead of using this lower-level method.
instead of using this lower-level method, or :meth:`spawn` to
also create the greenlet.
:keyword bool blocking: If True (the default), this function
:keyword bool blocking: If True (the default), this function
will block until the pool has space or a timeout occurs. If
will block until the pool has space or a timeout occurs. If
...
...
src/gevent/queue.py
View file @
b4db40b8
...
@@ -597,7 +597,7 @@ class Channel(object):
...
@@ -597,7 +597,7 @@ class Channel(object):
self
.
getters
.
remove
(
waiter
)
self
.
getters
.
remove
(
waiter
)
raise
raise
finally
:
finally
:
timeout
.
c
ancel
()
timeout
.
c
lose
()
def
get_nowait
(
self
):
def
get_nowait
(
self
):
return
self
.
get
(
False
)
return
self
.
get
(
False
)
...
...
src/gevent/threadpool.py
View file @
b4db40b8
...
@@ -219,7 +219,7 @@ class ThreadPool(GroupMappingMixin):
...
@@ -219,7 +219,7 @@ class ThreadPool(GroupMappingMixin):
# we get LoopExit (why?). Previously it was done with a rawlink on the
# we get LoopExit (why?). Previously it was done with a rawlink on the
# AsyncResult and the comment that it is "competing for order with get(); this is not
# AsyncResult and the comment that it is "competing for order with get(); this is not
# good, just make ThreadResult release the semaphore before doing anything else"
# good, just make ThreadResult release the semaphore before doing anything else"
thread_result
=
ThreadResult
(
result
,
hub
=
self
.
hub
,
call_when_ready
=
semaphore
.
release
)
thread_result
=
ThreadResult
(
result
,
self
.
hub
,
semaphore
.
release
)
task_queue
.
put
((
func
,
args
,
kwargs
,
thread_result
))
task_queue
.
put
((
func
,
args
,
kwargs
,
thread_result
))
self
.
adjust
()
self
.
adjust
()
except
:
except
:
...
@@ -333,6 +333,21 @@ class ThreadPool(GroupMappingMixin):
...
@@ -333,6 +333,21 @@ class ThreadPool(GroupMappingMixin):
# Always go to Greenlet because our self.spawn uses threads
# Always go to Greenlet because our self.spawn uses threads
return
True
return
True
class
_FakeAsync
(
object
):
def
send
(
self
):
pass
close
=
stop
=
send
def
__call_
(
self
,
result
):
"fake out for 'receiver'"
def
__bool__
(
self
):
return
False
__nonzero__
=
__bool__
_FakeAsync
=
_FakeAsync
()
class
ThreadResult
(
object
):
class
ThreadResult
(
object
):
...
@@ -340,9 +355,7 @@ class ThreadResult(object):
...
@@ -340,9 +355,7 @@ class ThreadResult(object):
__slots__
=
(
'exc_info'
,
'async_watcher'
,
'_call_when_ready'
,
'value'
,
__slots__
=
(
'exc_info'
,
'async_watcher'
,
'_call_when_ready'
,
'value'
,
'context'
,
'hub'
,
'receiver'
)
'context'
,
'hub'
,
'receiver'
)
def
__init__
(
self
,
receiver
,
hub
=
None
,
call_when_ready
=
None
):
def
__init__
(
self
,
receiver
,
hub
,
call_when_ready
):
if
hub
is
None
:
hub
=
get_hub
()
self
.
receiver
=
receiver
self
.
receiver
=
receiver
self
.
hub
=
hub
self
.
hub
=
hub
self
.
context
=
None
self
.
context
=
None
...
@@ -359,48 +372,45 @@ class ThreadResult(object):
...
@@ -359,48 +372,45 @@ class ThreadResult(object):
def
_on_async
(
self
):
def
_on_async
(
self
):
self
.
async_watcher
.
stop
()
self
.
async_watcher
.
stop
()
self
.
async_watcher
.
close
()
self
.
async_watcher
.
close
()
if
self
.
_call_when_ready
:
# Typically this is pool.semaphore.release and we have to
# Typically this is pool.semaphore.release and we have to
# call this in the Hub; if we don't we get the dreaded
# call this in the Hub; if we don't we get the dreaded
# LoopExit (XXX: Why?)
# LoopExit (XXX: Why?)
self
.
_call_when_ready
()
self
.
_call_when_ready
()
try
:
try
:
if
self
.
exc_info
:
if
self
.
exc_info
:
self
.
hub
.
handle_error
(
self
.
context
,
*
self
.
exc_info
)
self
.
hub
.
handle_error
(
self
.
context
,
*
self
.
exc_info
)
self
.
context
=
None
self
.
context
=
None
self
.
async_watcher
=
None
self
.
async_watcher
=
_FakeAsync
self
.
hub
=
None
self
.
hub
=
None
self
.
_call_when_ready
=
None
self
.
_call_when_ready
=
_FakeAsync
if
self
.
receiver
is
not
None
:
self
.
receiver
(
self
)
self
.
receiver
(
self
)
finally
:
finally
:
self
.
receiver
=
None
self
.
receiver
=
_FakeAsync
self
.
value
=
None
self
.
value
=
None
if
self
.
exc_info
:
if
self
.
exc_info
:
self
.
exc_info
=
(
self
.
exc_info
[
0
],
self
.
exc_info
[
1
],
None
)
self
.
exc_info
=
(
self
.
exc_info
[
0
],
self
.
exc_info
[
1
],
None
)
def
destroy
(
self
):
def
destroy
(
self
):
if
self
.
async_watcher
is
not
None
:
self
.
async_watcher
.
stop
()
self
.
async_watcher
.
stop
()
self
.
async_watcher
.
close
()
self
.
async_watcher
.
close
()
self
.
async_watcher
=
None
self
.
async_watcher
=
_FakeAsync
self
.
context
=
None
self
.
context
=
None
self
.
hub
=
None
self
.
hub
=
None
self
.
_call_when_ready
=
None
self
.
_call_when_ready
=
_FakeAsync
self
.
receiver
=
None
self
.
receiver
=
_FakeAsync
def
_ready
(
self
):
if
self
.
async_watcher
is
not
None
:
self
.
async_watcher
.
send
()
def
set
(
self
,
value
):
def
set
(
self
,
value
):
self
.
value
=
value
self
.
value
=
value
self
.
_ready
()
self
.
async_watcher
.
send
()
def
handle_error
(
self
,
context
,
exc_info
):
def
handle_error
(
self
,
context
,
exc_info
):
self
.
context
=
context
self
.
context
=
context
self
.
exc_info
=
exc_info
self
.
exc_info
=
exc_info
self
.
_ready
()
self
.
async_watcher
.
send
()
# link protocol:
# link protocol:
def
successful
(
self
):
def
successful
(
self
):
...
...
src/greentest/test__pool.py
View file @
b4db40b8
...
@@ -375,13 +375,15 @@ class TestPool(greentest.TestCase): # pylint:disable=too-many-public-methods
...
@@ -375,13 +375,15 @@ class TestPool(greentest.TestCase): # pylint:disable=too-many-public-methods
it
=
self
.
pool
.
imap_unordered
(
sqr_random_sleep
,
range
(
SMALL_RANGE
))
it
=
self
.
pool
.
imap_unordered
(
sqr_random_sleep
,
range
(
SMALL_RANGE
))
self
.
assertEqual
(
sorted
(
it
),
list
(
map
(
squared
,
range
(
SMALL_RANGE
))))
self
.
assertEqual
(
sorted
(
it
),
list
(
map
(
squared
,
range
(
SMALL_RANGE
))))
def
test_empty
(
self
):
def
test_empty
_imap_unordered
(
self
):
it
=
self
.
pool
.
imap_unordered
(
sqr
,
[])
it
=
self
.
pool
.
imap_unordered
(
sqr
,
[])
self
.
assertEqual
(
list
(
it
),
[])
self
.
assertEqual
(
list
(
it
),
[])
def
test_empty_imap
(
self
):
it
=
self
.
pool
.
imap
(
sqr
,
[])
it
=
self
.
pool
.
imap
(
sqr
,
[])
self
.
assertEqual
(
list
(
it
),
[])
self
.
assertEqual
(
list
(
it
),
[])
def
test_empty_map
(
self
):
self
.
assertEqual
(
self
.
pool
.
map
(
sqr
,
[]),
[])
self
.
assertEqual
(
self
.
pool
.
map
(
sqr
,
[]),
[])
def
test_terminate
(
self
):
def
test_terminate
(
self
):
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment