File: intro.txt

package info (click to toggle)
multiprocess 0.70.18-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 12,260 kB
  • sloc: python: 85,072; ansic: 8,840; makefile: 16
file content (354 lines) | stat: -rw-r--r-- 12,397 bytes parent folder | download | duplicates (49)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
.. include:: header.txt

==============
 Introduction
==============

Threads, processes and the GIL
==============================

To run more than one piece of code at the same time on the same
computer one has the choice of either using multiple processes or
multiple threads.

Although a program can be made up of multiple processes, these
processes are in effect completely independent of one another:
different processes are not able to cooperate with one another unless
one sets up some means of communication between them (such as by using
sockets).  If a lot of data must be transferred between processes then
this can be inefficient.
     
On the other hand, multiple threads within a single process are
intimately connected: they share their data but often can interfere
badly with one another.  It is often argued that the only way to make
multithreaded programming "easy" is to avoid relying on any shared
state and for the threads to only communicate by passing messages to
each other.

CPython has a *Global Interpreter Lock* (GIL) which in many ways makes
threading easier than it is in most languages by making sure that only
one thread can manipulate the interpreter's objects at a time.  As a
result, it is often safe to let multiple threads access data without
using any additional locking as one would need to in a language such
as C.

One downside of the GIL is that on multi-processor (or multi-core)
systems a multithreaded Python program can only make use of one
processor at a time.  This is a problem that can be overcome by using
multiple processes instead.

Python gives little direct support for writing programs using multiple
process.  This package allows one to write multi-process programs
using much the same API that one uses for writing threaded programs.


Forking and spawning
====================

There are two ways of creating a new process in Python:

* The current process can *fork* a new child process by using the
  `os.fork()` function.  This effectively creates an identical copy
  of the current process which is now able to go off and perform some
  task set by the parent process.  This means that the child process
  inherits *copies* of all variables that the parent process had.

  However, `os.fork()` is not available on every platform: in
  particular Windows does not support it.

* Alternatively, the current process can spawn a completely new Python
  interpreter by using the `subprocess` module or one of the
  `os.spawn*()` functions.

  Getting this new interpreter in to a fit state to perform the task
  set for it by its parent process is, however, a bit of a challenge.

The `processing` package uses `os.fork()` if it is available since
it makes life a lot simpler.  Forking the process is also more
efficient in terms of memory usage and the time needed to create the
new process.


The Process class
=================

In the `processing` package processes are spawned by creating a
`Process` object and then calling its `start()` method.
`processing.Process` follows the API of `threading.Thread`.  A
trivial example of a multiprocess program is ::

   from processing import Process     

   def f(name):
       print 'hello', name

   if __name__ == '__main__':
       p = Process(target=f, args=('bob',))
       p.start()
       p.join()

Here the function `f` is run in a child process. 

For an explanation of why (on Windows) the `if __name__ == '__main__'`
part is necessary see `Programming guidelines
<programming-guidelines.html>`_.


Exchanging objects between processes
====================================

`processing` supports two types of communication channel between
processes:

**Queues**:
    The function `Queue()` returns a near clone of `Queue.Queue`
    -- see the Python standard documentation.  For example ::

        from processing import Process, Queue

        def f(q):
            q.put([42, None, 'hello'])

        if __name__ == '__main__':
            q = Queue()
            p = Process(target=f, args=(q,))
            p.start()
            print q.get()    # prints "[42, None, 'hello']"
            p.join()

    Queues are thread and process safe.  See `Queues
    <processing-ref.html#pipes-and-queues>`_.

**Pipes**:
    The `Pipe()` function returns a pair of connection objects
    connected by a pipe which by default is duplex (two-way).  For
    example ::

        from processing import Process, Pipe

        def f(conn):
            conn.send([42, None, 'hello'])
            conn.close()

        if __name__ == '__main__':
            parent_conn, child_conn = Pipe()
            p = Process(target=f, args=(child_conn,))
            p.start()
            print parent_conn.recv()   # prints "[42, None, 'hello']"
            p.join()

    The two connection objects returned by `Pipe()` represent the two
    ends of the pipe.  Each connection object has `send()` and
    `recv()` methods (among others).  Note that data in a pipe may
    become corrupted if two processes (or threads) try to read from or
    write to the *same* end of the pipe at the same time.  Of course
    there is no risk of corruption from processes using different ends
    of the pipe at the same time.  See `Pipes
    <processing-ref.html#pipes-and-queues>`_.


Synchronization between processes
=================================

`processing` contains equivalents of all the synchronization
primitives from `threading`.  For instance one can use a lock to
ensure that only one process prints to standard output at a time::
       
       from processing import Process, Lock
       
       def f(l, i):
           l.acquire()
           print 'hello world', i
           l.release()
           
       if __name__ == '__main__':
           lock = Lock()
           
           for num in range(10):
               Process(target=f, args=(lock, num)).start()
               
Without using the lock output from the different processes is liable
to get all mixed up.


Sharing state between processes
===============================

As mentioned above, when doing concurrent programming it is usually
best to avoid using shared state as far as possible.  This is
particularly true when using multiple processes.

However, if you really do need to use some shared data then
`processing` provides a couple of ways of doing so.

**Shared memory**: 
   Data can be stored in a shared memory map using `Value` or `Array`.
   For example the following code ::
      
       from processing import Process, Value, Array

       def f(n, a):
           n.value = 3.1415927
           for i in range(len(a)):
               a[i] = -a[i]

       if __name__ == '__main__':
           num = Value('d', 0.0)
           arr = Array('i', range(10))
           
           p = Process(target=f, args=(num, arr))
           p.start()
           p.join()
           
           print num.value
           print arr[:]
           
   will print ::
        
       3.1415927
       [0, -1, -2, -3, -4, -5, -6, -7, -8, -9]

   The `'d'` and `'i'` arguments used when creating `num` and `arr`
   are typecodes of the kind used by the `array` module: `'d'`
   indicates a double precision float and `'i'` inidicates a signed
   integer.  These shared objects will be process and thread safe.

   For more flexibility in using shared memory one can use the
   `processing.sharedctypes` module which supports the creation of
   arbitrary `ctypes objects allocated from shared memory
   <sharedctypes.html>`_.

**Server process**:
   A manager object returned by `Manager()` controls a server process
   which holds python objects and allows other processes to manipulate
   them using proxies.

   A manager returned by `Manager()` will support types `list`,
   `dict`, `Namespace`, `Lock`, `RLock`, `Semaphore`,
   `BoundedSemaphore`, `Condition`, `Event`, `Queue`, `Value`
   and `Array`.  For example::

       from processing import Process, Manager

       def f(d, l):
           d[1] = '1'
           d['2'] = 2
           d[0.25] = None
           l.reverse()

       if __name__ == '__main__':
           manager = Manager()

           d = manager.dict()
           l = manager.list(range(10))

           p = Process(target=f, args=(d, l))
           p.start()
           p.join()

           print d
           print l

   will print ::

       {0.25: None, 1: '1', '2': 2}
       [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

   Creating managers which support other types is not hard --- see
   `Customized managers <manager-objects.html#customized-managers>`_.
   
   Server process managers are more flexible than using shared memory
   objects because they can be made to support arbitrary object types.
   Also, a single manager can be shared by processes on different
   computers over a network.  They are, however, slower than using
   shared memory.  See `Server process managers
   <manager-objects.html#server-process-managers>`_.


Using a pool of workers
=======================

The `Pool()` function returns an object representing a pool of worker
processes.  It has methods which allows tasks to be offloaded to the
worker processes in a few different ways.

For example::

    from processing import Pool

    def f(x):
        return x*x

    if __name__ == '__main__':
        pool = Pool(processes=4)              # start 4 worker processes
        result = pool.applyAsync(f, [10])     # evaluate "f(10)" asynchronously
        print result.get(timeout=1)           # prints "100" unless your computer is *very* slow
        print pool.map(f, range(10))          # prints "[0, 1, 4,..., 81]"

See `Process pools <pool-objects.html>`_.


Speed
=====

The following benchmarks were performed on a single core Pentium 4,
2.5Ghz laptop running Windows XP and Ubuntu Linux 6.10 --- see
`benchmarks.py <../examples/benchmarks.py>`_.


*Number of 256 byte string objects passed between processes/threads per sec*:

================================== ========== ==================
Connection type                    Windows    Linux
================================== ========== ==================
Queue.Queue                         49,000    17,000-50,000 [1]_
processing.Queue                    22,000    21,000
Queue managed by server              6,900     6,500
processing.Pipe                     52,000    57,000
================================== ========== ==================

.. [1] For some reason the performance of `Queue.Queue` is very
       variable on Linux.


*Number of acquires/releases of a lock per sec*:

============================== ========== ==========
Lock type                       Windows    Linux
============================== ========== ==========
threading.Lock                   850,000    560,000
processing.Lock                  420,000    510,000
Lock managed by server            10,000      8,400
threading.RLock                   93,000     76,000
processing.RLock                 420,000    500,000
RLock managed by server            8,800      7,400
============================== ========== ==========


*Number of interleaved waits/notifies per sec on a
condition variable by two processes*:

============================== ========== ==========
 Condition type                  Windows    Linux
============================== ========== ==========
threading.Condition             27,000     31,000
processing.Condition            26,000     25,000
Condition managed by server      6,600      6,000
============================== ========== ==========


*Number of integers retrieved from a sequence per sec*:

============================== ========== ==========
Sequence type                   Windows    Linux
============================== ========== ==========
list                           6,400,000  5,100,000
unsynchornized shared array    3,900,000  3,100,000
synchronized shared array        200,000    220,000
list managed by server            20,000     17,000
============================== ========== ==========

.. _Prev: index.html
.. _Up: index.html
.. _Next: processing-ref.html