|
| 1 | +.. _concurrency-howto: |
| 2 | + |
| 3 | +***************** |
| 4 | +Concurrency HOWTO |
| 5 | +***************** |
| 6 | + |
| 7 | +Python is a language the accommodates a variety of programming styles, |
| 8 | +from procedural to object-oriented to functional. The same applies |
| 9 | +to concurrency. Here we'll look at how different concurrency models |
| 10 | +look in Python, with an emphasis on practical workload-oriented examples. |
| 11 | + |
| 12 | +The following Python concurrency models are covered: |
| 13 | + |
| 14 | +* threads (:mod:`threading` and :mod:`concurrent.futures`) |
| 15 | +* multi-processing (:mod:`multiprocessing` and :mod:`concurrent.futures`) |
| 16 | +* async/await |
| 17 | +* CSP/actor model (:mod:`!interpreters`) |
| 18 | +* distributed (e.g. SMP) |
| 19 | + |
| 20 | +Each of these will be explained, with some simple examples. The later |
| 21 | +workload-oriented examples will be implemented using each, |
| 22 | +for comparison, when possible. |
| 23 | + |
| 24 | +.. note:: |
| 25 | + |
| 26 | + You should always make sure concurrency is the right tool for the job |
| 27 | + before you reach for it when solving your problem. There are many |
| 28 | + cases where concurrency simply isn't applicable or will only |
| 29 | + complicate the solution. In-depth discussion of this point |
| 30 | + is outside the scope of this document. |
| 31 | + |
| 32 | + |
| 33 | +All About Concurrency |
| 34 | +===================== |
| 35 | + |
| 36 | +What is concurrency? |
| 37 | +-------------------- |
| 38 | + |
| 39 | +At its most fundamental, concurrency means doing multiple things at once, |
| 40 | +from a strictly *logical* viewpoint. |
| 41 | + |
| 42 | +When a computer program runs, it executes a sequence of code in order. |
| 43 | +Sometimes it makes sense to break up that sequence into smaller pieces, |
| 44 | +where some of them can run independently of others. |
| 45 | + |
| 46 | +For example, consider the following program with three pieces:: |
| 47 | + |
| 48 | + prep |
| 49 | + do A |
| 50 | + do B |
| 51 | + |
| 52 | +If both ``do A`` and ``do B`` only rely on ``prep`` having completed, |
| 53 | +then we could rearrange the program in one of the following ways and |
| 54 | +end up with the same result:: |
| 55 | + |
| 56 | + prep = prep prep = prep ----- |
| 57 | + do B = do A do B = | | |
| 58 | + do A = = do A do B |
| 59 | + |
| 60 | +In the first alternative, we swap ``do A`` and ``do B``. In the second |
| 61 | +one we split the original program into two programs that we can run at |
| 62 | +the same time. In the third one, we run ``do A`` and ``do B`` at the |
| 63 | +same time. "At the same time" means concurrency. |
| 64 | + |
| 65 | +Concurrency often involves some degree of synchronization between |
| 66 | +the tasks. At the most basic conceptual level: one task may wait |
| 67 | +for another to finish. |
| 68 | + |
| 69 | +In addition to code running at the same time, concurrency typically |
| 70 | +also involves some amount of resources shared between the concurrent |
| 71 | +tasks. That may include memory, files, and sockets. |
| 72 | + |
| 73 | +What is parallelism? |
| 74 | +-------------------- |
| 75 | + |
| 76 | +Concurrency may happen in one of two ways. The concurrent tasks may |
| 77 | +share a single CPU, each running a little bit at a time, with the |
| 78 | +operating system (or language runtime) taking care of the switching. |
| 79 | +The other way is where each task runs on its own CPU, meaning they |
| 80 | +are physically running at the same time, not just logically. |
| 81 | + |
| 82 | +That second way is parallelism. |
| 83 | + |
| 84 | +What problems can concurrency help solve? |
| 85 | +----------------------------------------- |
| 86 | + |
| 87 | +Primarily, concurrency can be helpful by making your program faster |
| 88 | +and more responsive (less latency), when possible. In other words, |
| 89 | +you get better computational throughput. That happens by enabling |
| 90 | +the following: |
| 91 | + |
| 92 | +* run on multiple CPU cores (parallelism) |
| 93 | +* keep blocking resources from blocking the whole program |
| 94 | +* make sure critical tasks have priority |
| 95 | +* process results as they come, instead of waiting for them all |
| 96 | + |
| 97 | +Other possible benefits: |
| 98 | + |
| 99 | +* asynchronous events can be handled more cleanly |
| 100 | +* better efficiency using hardware resources |
| 101 | +* improved scalability |
| 102 | + |
| 103 | +What are the downsides? |
| 104 | +----------------------- |
| 105 | + |
| 106 | +The main challenge when using concurrency is the extra complexity. |
| 107 | + |
| 108 | +.. XXX |
| 109 | +
|
| 110 | +* races on shared resources |
| 111 | +* error handling |
| 112 | +* ... |
| 113 | + |
| 114 | +The operating system, along with some libraries and frameworks, |
| 115 | +can help mitigate the extra complexity. So can the concurrency |
| 116 | +model you use, which we'll talk about a little later.. |
| 117 | + |
| 118 | +Workloads |
| 119 | +--------- |
| 120 | + |
| 121 | +We've looked at what you can do with concurrency from a high level. |
| 122 | +Now let's look at some concrete examples. |
| 123 | + |
| 124 | + |
| 125 | +... |
| 126 | + |
| 127 | + |
| 128 | +Concurrency Models |
| 129 | +------------------ |
| 130 | + |
| 131 | +The concept of concurrency has been a part of the study and practice |
| 132 | +of computer software since the 1950s and 1960s, with various innovations |
| 133 | +since then. The application of the different theoretical concurrency |
| 134 | +models can be categorized as follows: |
| 135 | + |
| 136 | +* free threads - using multiple threads in the same process, |
| 137 | + with no isolation between them |
| 138 | +* isolated threads - threads with strict isolation between them |
| 139 | + (e.g. CSP and actor model) |
| 140 | +* multiprocessing - using multiple isolated processes |
| 141 | +* distributed - multiprocessing across multiple computers |
| 142 | +* async/await - using coroutines (AKA cooperative multitasking) |
| 143 | + |
| 144 | +(There are certainly others, but these are the focus here.) |
| 145 | + |
| 146 | +There are tradeoffs to each. Free-threading probably has the most |
| 147 | +notoriety and the most examples, but is also the most likely to cause |
| 148 | +you pain. |
| 149 | +Isolated threads have few of the downsides but are less familiar. |
| 150 | +Multiprocessing and distributed are less efficient at smaller scales. |
| 151 | +Async can be straightforward, but may cascade throughout a code base, |
| 152 | +doesn't necessarily give you parallelism. |
| 153 | + |
| 154 | + |
| 155 | +Python Concurrency Models |
| 156 | +========================= |
| 157 | + |
| 158 | +We've looked at concurrency and concurrency models generally. |
| 159 | +Now let's see what they look like in Python. |
| 160 | + |
| 161 | +Free-threading |
| 162 | +-------------- |
| 163 | + |
| 164 | +The stdlib :mod:`threading` module ... |
| 165 | + |
| 166 | +... |
| 167 | + |
| 168 | +Multi-processing |
| 169 | +---------------- |
| 170 | + |
| 171 | +... |
| 172 | + |
| 173 | +Async/Await |
| 174 | +----------- |
| 175 | + |
| 176 | +... |
| 177 | + |
| 178 | +Isolated Threads (CSP/Actor Model) |
| 179 | +---------------------------------- |
| 180 | + |
| 181 | +... |
| 182 | + |
| 183 | +Distributed |
| 184 | +----------- |
| 185 | + |
| 186 | +... |
| 187 | + |
| 188 | + |
| 189 | +Python Concurrency Workloads |
| 190 | +============================ |
| 191 | + |
| 192 | +... |
| 193 | + |
| 194 | +also see: |
| 195 | + |
| 196 | +* https://github.com/faster-cpython/ideas/wiki/Tables:-Workloads |
| 197 | +* https://github.com/ericsnowcurrently/concurrency-benchmarks |
| 198 | + |
| 199 | + |
| 200 | +Workload 1 |
| 201 | +---------- |
| 202 | + |
| 203 | +# ... |
| 204 | + |
| 205 | +.. raw:: html |
| 206 | + |
| 207 | + <style> |
| 208 | + table.workload-example th |
| 209 | + { |
| 210 | + vertical-align: top; |
| 211 | + } |
| 212 | +
|
| 213 | + table.workload-example td |
| 214 | + { |
| 215 | + vertical-align: top; |
| 216 | + } |
| 217 | + </style> |
| 218 | + |
| 219 | +.. list-table:: |
| 220 | + :header-rows: 1 |
| 221 | + :class: borderless workload-example |
| 222 | + :align: left |
| 223 | + |
| 224 | + * - threads |
| 225 | + - multiple interpreters |
| 226 | + - async/await |
| 227 | + - multiple processes |
| 228 | + - SMP |
| 229 | + * - .. raw:: html |
| 230 | + |
| 231 | + <details> |
| 232 | + <summary>(expand)</summary> |
| 233 | + |
| 234 | + .. literalinclude:: ../includes/concurrency.py |
| 235 | + :name: concurrency-workload-1-threads |
| 236 | + :start-after: [start-w1-threads] |
| 237 | + :end-before: [end-w1-threads] |
| 238 | + :dedent: |
| 239 | + :linenos: |
| 240 | + |
| 241 | + .. raw:: html |
| 242 | + |
| 243 | + </details> |
| 244 | + |
| 245 | + - .. raw:: html |
| 246 | + |
| 247 | + <details> |
| 248 | + <summary>(expand)</summary> |
| 249 | + |
| 250 | + .. literalinclude:: ../includes/concurrency.py |
| 251 | + :name: concurrency-workload-1-subinterpreters |
| 252 | + :start-after: [start-w1-subinterpreters] |
| 253 | + :end-before: [end-w1-subinterpreters] |
| 254 | + :dedent: |
| 255 | + :linenos: |
| 256 | + |
| 257 | + .. raw:: html |
| 258 | + |
| 259 | + </details> |
| 260 | + |
| 261 | + - .. raw:: html |
| 262 | + |
| 263 | + <details> |
| 264 | + <summary>(expand)</summary> |
| 265 | + |
| 266 | + .. literalinclude:: ../includes/concurrency.py |
| 267 | + :name: concurrency-workload-1-async |
| 268 | + :start-after: [start-w1-async] |
| 269 | + :end-before: [end-w1-async] |
| 270 | + :dedent: |
| 271 | + :linenos: |
| 272 | + |
| 273 | + .. raw:: html |
| 274 | + |
| 275 | + </details> |
| 276 | + |
| 277 | + - .. raw:: html |
| 278 | + |
| 279 | + <details> |
| 280 | + <summary>(expand)</summary> |
| 281 | + |
| 282 | + .. literalinclude:: ../includes/concurrency.py |
| 283 | + :name: concurrency-workload-1-multiprocessing |
| 284 | + :start-after: [start-w1-multiprocessing] |
| 285 | + :end-before: [end-w1-multiprocessing] |
| 286 | + :dedent: |
| 287 | + :linenos: |
| 288 | + |
| 289 | + .. raw:: html |
| 290 | + |
| 291 | + </details> |
| 292 | + |
| 293 | + - .. raw:: html |
| 294 | + |
| 295 | + <details> |
| 296 | + <summary>(expand)</summary> |
| 297 | + |
| 298 | + .. literalinclude:: ../includes/concurrency.py |
| 299 | + :name: concurrency-workload-1-smp |
| 300 | + :start-after: [start-w1-smp] |
| 301 | + :end-before: [end-w1-smp] |
| 302 | + :dedent: |
| 303 | + :linenos: |
| 304 | + |
| 305 | + .. raw:: html |
| 306 | + |
| 307 | + </details> |
| 308 | + |
| 309 | +Using :mod:`concurrent.futures`: |
| 310 | + |
| 311 | +.. raw:: html |
| 312 | + |
| 313 | + <details> |
| 314 | + <summary>(expand)</summary> |
| 315 | + |
| 316 | +.. literalinclude:: ../includes/concurrency.py |
| 317 | + :name: concurrency-workload-1-concurrent-futures-thread |
| 318 | + :start-after: [start-w1-concurrent-futures-thread] |
| 319 | + :end-before: [end-w1-concurrent-futures-thread] |
| 320 | + :dedent: |
| 321 | + :linenos: |
| 322 | + |
| 323 | +.. raw:: html |
| 324 | + |
| 325 | + </details> |
| 326 | + |
| 327 | +Workload 2 |
| 328 | +---------- |
| 329 | + |
| 330 | +... |
0 commit comments