Skip to content

Commit a83b277

Browse files
committed
Pools, Groups, Semaphore and Locks. Oh my!
1 parent 31e4e58 commit a83b277

File tree

2 files changed

+392
-49
lines changed

2 files changed

+392
-49
lines changed

index.html

Lines changed: 217 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,7 @@ <h3 class="author">
157157
<li><a href="#queues">Queues</a></li>
158158
<li><a href="#groups-and-pools">Groups and Pools</a></li>
159159
<li><a href="#locks-and-semaphores">Locks and Semaphores</a></li>
160-
<li><a href="#thread-locals">Thread Locals</a><ul>
161-
<li><a href="#werkzeug">Werkzeug</a></li>
162-
</ul>
163-
</li>
160+
<li><a href="#thread-locals">Thread Locals</a></li>
164161
<li><a href="#actors">Actors</a></li>
165162
</ul>
166163
</li>
@@ -205,20 +202,21 @@ <h2 id="greenlets">Greenlets</h2>
205202
<p>The primary pattern used in gevent is the <strong>Greenlet</strong>, a
206203
lightweight coroutine provided to Python as a C extension module.
207204
Greenlets all run inside of the OS process for the main
208-
program but are scheduled cooperatively. This differs from any of
209-
the real parallelism constructs provided by <code>multiprocessing</code> or
210-
<code>multithreading</code> libraries which do spin processes and POSIX threads
211-
which are truly parallel.</p>
205+
program but are scheduled cooperatively. </p>
206+
<blockquote>
207+
<p>Only one greenlet is ever running at any given time.</p>
208+
</blockquote>
209+
<p>This differs from any of the real parallelism constructs provided by
210+
<code>multiprocessing</code> or <code>threading</code> libraries which do spin processes
211+
and POSIX threads which are scheduled by the operating system and
212+
are truly parallel.</p>
212213
<h2 id="synchronous-asynchronous-execution">Synchronous &amp; Asynchronous Execution</h2>
213-
<p>The core idea of concurrency is that a larger task can be broken
214-
down into a collection of subtasks whose operation does not
215-
depend on the other tasks and thus can be run
216-
<em>asynchronously</em> instead of one at a time
217-
<em>synchronously</em>. A switch between the two
218-
executions is known as a <em>context switch</em>.</p>
219-
<p>A context switch in gevent is done through
220-
<em>yielding</em>. In this case example we have
221-
two contexts which yield to each other through invoking
214+
<p>The core idea of concurrency is that a larger task can be broken down
215+
into a collection of subtasks whose and scheduled to run simultaneously
216+
or <em>asynchronously</em>, instead of one at a time or <em>synchronously</em>. A
217+
switch between the two subtasks is known as a <em>context switch</em>.</p>
218+
<p>A context switch in gevent is done through <em>yielding</em>. In this case
219+
example we have two contexts which yield to each other through invoking
222220
<code>gevent.sleep(0)</code>.</p>
223221
<pre><code class="python">
224222
import gevent
@@ -255,6 +253,8 @@ <h2 id="synchronous-asynchronous-execution">Synchronous &amp; Asynchronous Execu
255253
libraries will implicitly yield their greenlet contexts whenever
256254
possible. I cannot stress enough what a powerful idiom this is.
257255
But maybe an example will illustrate.</p>
256+
<p>In this case the <code>select()</code> function is normally a blocking
257+
call that polls on various file descriptors.</p>
258258
<pre><code class="python">
259259
import time
260260
import gevent
@@ -294,7 +294,7 @@ <h2 id="synchronous-asynchronous-execution">Synchronous &amp; Asynchronous Execu
294294
Ended Polling: at 2.0 seconds
295295
Ended Polling: at 2.0 seconds
296296
</pre></code></p>
297-
<p>A somewhat synthetic example defines a <code>task</code> function
297+
<p>Another somewhat synthetic example defines a <code>task</code> function
298298
which is <em>non-deterministic</em>
299299
(i.e. its output is not guaranteed to give the same result for
300300
the same inputs). In this case the side effect of running the
@@ -339,16 +339,16 @@ <h2 id="synchronous-asynchronous-execution">Synchronous &amp; Asynchronous Execu
339339
Task 8 done
340340
Task 9 done
341341
Asynchronous:
342-
Task 0 done
343-
Task 2 done
342+
Task 9 done
344343
Task 6 done
345-
Task 5 done
344+
Task 3 done
346345
Task 4 done
347-
Task 7 done
348346
Task 8 done
349-
Task 9 done
347+
Task 5 done
350348
Task 1 done
351-
Task 3 done
349+
Task 7 done
350+
Task 0 done
351+
Task 2 done
352352
</pre></code></p>
353353
<p>In the synchronous case all the tasks are run sequentially,
354354
which results in the main programming <em>blocking</em> (
@@ -407,10 +407,10 @@ <h2 id="synchronous-asynchronous-execution">Synchronous &amp; Asynchronous Execu
407407
</pre>
408408

409409
<h2 id="determinism">Determinism</h2>
410-
<p>As mentioned previously, greenlets are deterministic. Given the
411-
same inputs and they always produce the same output. For example
412-
lets spread a task across a multiprocessing pool compared to a
413-
gevent pool.</p>
410+
<p>As mentioned previously, greenlets are deterministic. Given the same
411+
configuration of greenlets and the same set of inputs and they always
412+
produce the same output. For example lets spread a task across a
413+
multiprocessing pool compared to a gevent pool.</p>
414414
<pre>
415415
<code class="python">
416416
import time
@@ -910,9 +910,197 @@ <h2 id="queues">Queues</h2>
910910
Quitting time!
911911
</pre></code></p>
912912
<h2 id="groups-and-pools">Groups and Pools</h2>
913+
<p>A group is a collection of running greenlets which are managed
914+
and scheduled together as group. It also doubles as parallel
915+
dispatcher that mirrors the Python <code>multiprocessing</code> library.</p>
916+
<pre><code class="python">
917+
import gevent
918+
from gevent.pool import Group
919+
920+
def talk(msg):
921+
for i in xrange(3):
922+
print(msg)
923+
924+
g1 = gevent.spawn(talk, 'bar')
925+
g2 = gevent.spawn(talk, 'foo')
926+
g3 = gevent.spawn(talk, 'fizz')
927+
928+
group = Group()
929+
group.add(g1)
930+
group.add(g2)
931+
group.join()
932+
933+
group.add(g3)
934+
group.join()
935+
</pre>
936+
937+
<p></code>
938+
<pre><code class="python">
939+
bar
940+
bar
941+
bar
942+
foo
943+
foo
944+
foo
945+
fizz
946+
fizz
947+
fizz
948+
</pre></code></p>
949+
<p>This is very usefull for managing groups of asynchronous tasks
950+
that.</p>
951+
<p>As mentioned above Group also provides an API for dispatching
952+
jobs to grouped greenlets and collecting their results in various
953+
ways.</p>
954+
<pre><code class="python">
955+
import gevent
956+
from gevent import getcurrent
957+
from gevent.pool import Group
958+
959+
group = Group()
960+
961+
def hello_from(n):
962+
print('Size of group', len(group))
963+
print('Hello from Greenlet %s' % id(getcurrent()))
964+
965+
group.map(hello_from, xrange(3))
966+
967+
def intensive(n):
968+
gevent.sleep(3 - n)
969+
return 'task', n
970+
971+
print('Ordered')
972+
973+
ogroup = Group()
974+
for i in ogroup.imap(intensive, xrange(3)):
975+
print(i)
976+
977+
print('Unordered')
978+
979+
igroup = Group()
980+
for i in igroup.imap_unordered(intensive, xrange(3)):
981+
print(i)
982+
983+
</pre>
984+
985+
<p></code>
986+
<pre><code class="python">
987+
Size of group 3
988+
Hello from Greenlet 140728641091376
989+
Size of group 3
990+
Hello from Greenlet 140728641090736
991+
Size of group 3
992+
Hello from Greenlet 140728641091696
993+
Ordered
994+
('task', 0)
995+
('task', 1)
996+
('task', 2)
997+
Unordered
998+
('task', 2)
999+
('task', 1)
1000+
('task', 0)
1001+
</pre></code></p>
1002+
<p>A pool is a structure designed for handling dynamic numbers of
1003+
greenlets which need to be concurrency-limited. This is often
1004+
desirable in cases where one wants to do many network or IO bound
1005+
tasks in parallel.</p>
1006+
<pre><code class="python">
1007+
import gevent
1008+
from gevent import getcurrent
1009+
from gevent.pool import Pool
1010+
1011+
pool = Pool(2)
1012+
1013+
def hello_from(n):
1014+
print('Size of pool', len(pool))
1015+
1016+
pool.map(hello_from, xrange(3))
1017+
</pre>
1018+
1019+
<p></code>
1020+
<pre><code class="python">
1021+
Size of pool 2
1022+
Size of pool 2
1023+
Size of pool 1
1024+
</pre></code></p>
1025+
<p>Often when building gevent driven services one will center the
1026+
entire service around a pool structure. An example might be a
1027+
class which polls on various sockets.</p>
1028+
<pre>
1029+
<code class="python">from gevent.pool import Pool
1030+
1031+
class SocketPool(object):
1032+
1033+
def __init__(self):
1034+
self.pool = Pool(1000)
1035+
self.pool.start()
1036+
1037+
def listen(self, socket):
1038+
while True:
1039+
socket.recv()
1040+
1041+
def add_handler(self, socket):
1042+
if self.pool.full():
1043+
raise Exception("At maximum pool size")
1044+
else:
1045+
self.pool.spawn(self.listen, socket)
1046+
1047+
def shutdown(self):
1048+
self.pool.kill()
1049+
1050+
</code>
1051+
</pre>
1052+
9131053
<h2 id="locks-and-semaphores">Locks and Semaphores</h2>
1054+
<p>A semaphore is a low level synchronization primitive that allows
1055+
greenlets to coordinate and limit concurrent access or execution. A
1056+
semaphore exposes two methods, <code>acquire</code> and <code>release</code> The
1057+
difference between the number of times and a semaphore has been
1058+
acquired and released is called the bound of the semaphore. If a
1059+
semaphore bound reaches 0 it will block until another greenlet
1060+
releases its acquisition.</p>
1061+
<pre><code class="python">
1062+
from gevent import sleep
1063+
from gevent.pool import Pool
1064+
from gevent.coros import BoundedSemaphore
1065+
1066+
sem = BoundedSemaphore(2)
1067+
1068+
def worker1(n):
1069+
sem.acquire()
1070+
print('Worker %i acquired semaphore' % n)
1071+
sleep(0)
1072+
sem.release()
1073+
print('Worker %i released semaphore' % n)
1074+
1075+
def worker2(n):
1076+
with sem:
1077+
print('Worker %i acquired semaphore' % n)
1078+
sleep(0)
1079+
print('Worker %i released semaphore' % n)
1080+
1081+
pool = Pool()
1082+
pool.map(worker1, xrange(0,2))
1083+
pool.map(worker2, xrange(3,6))
1084+
</pre>
1085+
1086+
<p></code>
1087+
<pre><code class="python">
1088+
Worker 0 acquired semaphore
1089+
Worker 1 acquired semaphore
1090+
Worker 0 released semaphore
1091+
Worker 1 released semaphore
1092+
Worker 3 acquired semaphore
1093+
Worker 4 acquired semaphore
1094+
Worker 3 released semaphore
1095+
Worker 4 released semaphore
1096+
Worker 5 acquired semaphore
1097+
Worker 5 released semaphore
1098+
</pre></code></p>
1099+
<p>A semaphore with bound of 1 is known as a Lock. it provides
1100+
exclusive execution to one greenlet. They are often used to
1101+
ensure that resources are only in use at one time in the context
1102+
of a program.</p>
9141103
<h2 id="thread-locals">Thread Locals</h2>
915-
<h3 id="werkzeug">Werkzeug</h3>
9161104
<h2 id="actors">Actors</h2>
9171105
<p>The actor model is a higher level concurrency model popularized
9181106
by the language Erlang. In short the main idea is that you have a

0 commit comments

Comments
 (0)