Skip to content

Commit ccdd898

Browse files
author
Kristian Rother
committed
edit concurrency chapter
1 parent d87b061 commit ccdd898

File tree

4 files changed

+119
-28
lines changed

4 files changed

+119
-28
lines changed

concurrency/README.rst

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,23 @@ good reasons to be very cautious with it.
88

99
----
1010

11-
Why is Concurrency risky?
12-
-------------------------
11+
Pros and Cons of Concurrency
12+
----------------------------
1313

14-
Many times, the devil is so much in the details that it is even a bad idea.
14+
Often concurrency is a bad idea. The devil is lurking in the details:
1515

1616
- Coordinating parallel sub-programs is very difficult to debug (look up the words *“race condition”* and *“heisenbug”*).
17-
- Starting multiple Python processes instead is really easy (e.g. from a batch script or the `multiprocessing` module).
1817
- Python has a strange thing called the **GIL (Global Interpreter Lock)**. That means, Python can really only execute one command at a time.
19-
- There are great existing solutions for the most typical applications.
18+
- There are great existing solutions for many typical applications (web scraping, web servers).
2019

21-
----
22-
23-
When could concurrency be a good idea?
24-
--------------------------------------
20+
On the other hand, concurrency can be a good idea:
2521

26-
Here is one example:
22+
- if your tasks are waiting for some I/O anyway, the speed of Python does not matter.
23+
- starting multiple separate Python processes is rather easy (with the `multiprocessing` module).
24+
- if you are looking for a challenge.
2725

28-
You are writing a computer game for fun and would like many sprites to
29-
move at the same time **AND** you are looking for difficult problems to solve.
30-
31-
There are two noteworthy approaches to concurrency in Python:
32-
**threads** and **coroutines**.
26+
There are three noteworthy approaches to concurrency in Python:
27+
**threads**, **coroutines** and **multiple processes**.
3328

3429
----
3530

@@ -64,14 +59,19 @@ This is the most flexible approach, but also has the highest overhead.
6459

6560
.. literalinclude:: factorial.py
6661

67-
6862
----
6963

70-
Alternatives
71-
------------
64+
Challenge: Gaussian Elimination
65+
-------------------------------
66+
67+
In :download:`gauss_elim.py` you find an implementation of the `Gauss Elimination Algorithm <https://en.wikipedia.org/wiki/Gaussian_elimination>`__ to solve linear equation systems.
68+
The algorithm has a **cubic time complexity**.
69+
70+
Parallelize the execution of the algorithm and check whether it gets any faster.
71+
72+
In :download:`test_gauss_elim.py` you find unit tests for the module.
7273

73-
- if you want to read/write data, use a database
74-
- if you want to scrape web pages, use the ``scrapy`` framework.
75-
- if you want to build a web server, use ``FastAPI`, ``Flask`` or ``Django``.
76-
- if you want to do number crunching, use ``Spark``, ``Dask``, ``Pytorch`` or ``Tensorflow``
74+
.. note::
7775

76+
The linear equation solver is written in plain Python.
77+
Of course, Numpy would also speed up the execution considerably.

concurrency/gauss_elim.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
2+
from copy import deepcopy
3+
import numpy as np
4+
5+
def solve_linear(data: list[float]) -> list[float]:
6+
"""solves linear equations with the Gauss Elimination method"""
7+
nrows = len(data)
8+
ncols = len(data[0])
9+
M = deepcopy(data)
10+
for i in range(ncols - 1):
11+
# normalize each row by position i
12+
for row in M[i:]:
13+
first = row[i]
14+
for j in range(i, ncols):
15+
row[j] = row[j] / first
16+
# subtract equation i from all
17+
for j in range(i + 1, nrows):
18+
for k in range(i, ncols):
19+
M[j][k] -= M[i][k]
20+
21+
# retrieve coefficients from triangular form
22+
coef = [0.0] * nrows
23+
for i in range(nrows - 1, -1, -1): # process rows in reverse order
24+
temp = 0.0
25+
for j in range(i + 1, ncols - 1):
26+
temp -= M[i][j] * coef[j]
27+
temp += M[i][-1]
28+
coef[i] = temp
29+
30+
return coef
31+
32+
33+
def create_linear_equation_sys(size):
34+
"""creates a solvable linear equation system"""
35+
A = np.random.rand(size, size)
36+
# Ensure matrix A is invertible by checking the determinant
37+
while np.linalg.det(A) == 0:
38+
A = np.random.rand(size, size)
39+
40+
solution = np.random.randint(-100, 100, size=size) # random solution vector
41+
42+
b = np.dot(A, solution) # right-hand-side vector
43+
44+
mtx = np.hstack([A, b.reshape(-1, 1)])
45+
return mtx
46+
47+
def round_output(v, digits=3):
48+
return [round(x, digits) for x in v]
49+
50+
if __name__ == '__main__':
51+
# solve subsequent tasks (slow)
52+
tasks = [create_linear_equation_sys(200) for _ in range(10)]
53+
for i, M in enumerate(tasks, 1):
54+
print(f"\ntask {i}")
55+
result = round_output(solve_linear(M))
56+
print("done")

concurrency/test_gauss_elim.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
2+
import pytest
3+
4+
from gauss_elim import solve_linear
5+
6+
7+
EXAMPLES = [
8+
(
9+
[
10+
[2, 3, 3, 3],
11+
[3, -2, -9, 4],
12+
[5, -1, -18, -1],
13+
],
14+
[3, -2, 1]),
15+
(
16+
[
17+
[1, 2, 3],
18+
[4, 1, 5],
19+
],
20+
[1, 1],
21+
),
22+
(
23+
[
24+
[1, 2, 3],
25+
[1, 2, 3],
26+
],
27+
[1, 1],
28+
),
29+
]
30+
31+
@pytest.mark.parametrize("matrix,expected", EXAMPLES)
32+
def test_solve(matrix, expected):
33+
result = solve_linear(matrix)
34+
result = [round(x, 3) for x in result]
35+
assert result == expected

concurrency/thread_factorial.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ def __init__(self, number):
1515
self.number = number
1616

1717
@staticmethod
18-
def fibo(n):
19-
if n == 0:
20-
return 1
21-
else:
22-
return n * FactorialThread.fibo(n - 1)
18+
def factorial(n):
19+
return (
20+
1 if n == 0
21+
else n * FactorialThread.factorial(n - 1)
22+
)
2323

2424
def run(self):
25-
result = self.fibo(self.number)
25+
result = self.factorial(self.number)
2626
time.sleep(random.randint(5, 20))
2727
print(f"{self.number}! = {result}")
2828

0 commit comments

Comments
 (0)