├── .gitignore
├── LICENSE
├── Multi-processing and Multi-threading in Python
├── Multi-processing
│ ├── Multi-processing in Python.md
│ ├── distributed_processing_server.py
│ ├── distributed_processing_worker.py
│ └── multiprocessing_async.py
└── Multi-threading
│ ├── Locks and Sync Blocking
│ ├── sync_blocking.py
│ └── wait_blocking.py
│ ├── Multi-threading in Python.md
│ ├── Race Condition Demo by Raymond Hettinger
│ ├── comm_via_atomic_message_queue.py
│ ├── comm_via_locks.py
│ ├── note
│ └── race_condition.py
│ └── multithreading_async.py
├── Multi-threading in Java
├── AtomicIntegerDemo.java
├── Bank User Example from 15-619 Cloud Computing
│ ├── BankUserMultiThreaded.java
│ ├── F17 15-619 Cloud Computing- (writeup: Thread-safe programming in Java) - TheProject.Zone.pdf
│ └── S17 15-619 Cloud Computing- (writeup: Introduction to multithreaded programming in Java) - TheProject.Zone.pdf
├── BlockingQueueDemo.java
├── Locks and Sync Blocking
│ └── WaitBlockingDemo.java
└── Multi-threading in Java.md
├── README.md
├── distributed_locking.py
├── thread_notify.png
├── thread_status.png
└── thread_wait.png
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | # Java compilation files
4 | *.class
5 |
6 | # Python compilation files
7 | __pycache__/
8 | *.pyc
9 |
10 | # Project files
11 | .vscode/
12 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Ziang Lu
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Multi-processing and Multi-threading in Python/Multi-processing/Multi-processing in Python.md:
--------------------------------------------------------------------------------
1 | # Multi-processing in Python
2 |
3 | ## Basic Implementations
4 |
5 | * Using `os.fork()` function
6 |
7 | ```python
8 | import os
9 |
10 | print(f'Process (os.getpid()) start...')
11 | pid = os.fork() # 操作系统把当前进程(父进程)复制一份(称为子进程)
12 | # os.fork()函数调用一次, 返回两次: 父进程中返回创建的子进程的ID, 子进程中返回0
13 | if pid != 0:
14 | print(f'I ({os.getpid()}) just created a child process {pid}.')
15 | else:
16 | print(f'I am child process ({os.getpid()}) and my parent is {os.getppid()}.')
17 |
18 | # Output:
19 | # Process (15918) start...
20 | # I (15918) just created a child process (15919).
21 | # I am child process (15919) and my parent is 15918.
22 | ```
23 |
24 | * Using `multiprocessing` module
25 |
26 | * Create subprocesses using `Process`
27 |
28 | ```python
29 | from multiprocessing import Process
30 |
31 |
32 | def run_process(name: str) -> None:
33 | """
34 | Dummy task to be run within a process.
35 | :param name: str
36 | :return: None
37 | """
38 | print(f"Running child process '{name}' ({os.getpid()})")
39 |
40 |
41 | print(f'Parent process {os.getpid()}')
42 | p = Process(target=run_process, args=('test',))
43 | print('Child process starting...')
44 | p.start()
45 | p.join()
46 | print('Child process ends.')
47 | print('Parent process ends.')
48 |
49 | # Output:
50 | # Parent process 16623
51 | # Child process starting...
52 | # Running child process 'test' (16624)
53 | # Child process ends.
54 | # Parent process ends.
55 | ```
56 |
57 | * Create a pool of subprocesses using `Pool` and `Pool.apply()` method, `Pool.map()` method, `Pool.imap()` method or `Pool.imap_unordered()` method
58 |
59 | ```python
60 | import os
61 | import random
62 | import time
63 | from multiprocessing import Pool
64 |
65 |
66 | def long_time_task(name: str) -> float:
67 | """
68 | Dummy long task to be run within a process.
69 | :param name: str
70 | :return: float
71 | """
72 | print(f"Running task '{name}' ({os.getpid()})...")
73 | start = time.time()
74 | time.sleep(random.random() * 3)
75 | end = time.time()
76 | time_elapsed = end - start
77 | print(f"Task '{name}' runs {time_elapsed:.2f} seconds.")
78 | return time_elapsed
79 |
80 |
81 | print(f'Parent process {os.getpid()}')
82 | with Pool(4) as pool: # 开启一个4个进程的进程池
83 | # 在进程池中执行一个任务
84 | # pool.apply(func=long_time_task, args=(f'Some Task')) # Will block here
85 |
86 | # 在进程池中concurrently执行多个相同的任务, 只是参数不同
87 | start = time.time()
88 | # results = pool.map(
89 | # func=long_time_task, iterable=map(lambda x: f'Task-{x}', range(5))
90 | # ) # Will block here
91 | total_running_time = 0
92 | # for result in pool.imap(
93 | # func=long_time_task, iterable=map(lambda x: f'Task-{x}', range(5))
94 | # ): # Lazy version of pool.map()
95 | # total_running_time += result
96 | for result in pool.imap_unordered(
97 | func=long_time_task, iterable=map(lambda x: f'Task-{x}', range(5))
98 | ): # Lazy, unordered version of pool.map()
99 | total_running_time += result
100 | pool.close() # 调用close()之后就不能再添加新的任务了
101 | pool.join()
102 | print(f'Theoretical total running time: {total_running_time:.2f} seconds.')
103 | end = time.time()
104 | print(f'Actual running time: {end - start:.2f} seconds.')
105 | print('All subprocesses done.')
106 |
107 | # Output:
108 | # Parent process 9727
109 | # Running task 'Task-0' (9728)...
110 | # Running task 'Task-1' (9729)...
111 | # Running task 'Task-2' (9730)...
112 | # Running task 'Task-3' (9731)...
113 | # Task 'Task-0' runs 0.23 seconds.
114 | # Running task 'Task-4' (9728)...
115 | # Task 'Task-3' runs 0.44 seconds.
116 | # Task 'Task-4' runs 1.39 seconds.
117 | # Task 'Task-1' runs 1.90 seconds.
118 | # Task 'Task-2' runs 1.89 seconds.
119 | # Theoretical total running time: 5.85 seconds.
120 | # Actual running time: 1.95 seconds.
121 | # All subprocesses done.
122 | ```
123 |
124 | ***
125 |
126 | **Multi-processing + Async (Subprocess [Coroutine])**
127 |
128 | -> 把coroutine包在subprocess中
129 |
130 | *创建一个process pool (subprocesses), 在其中放入async的task (coroutine), 参见`multiprocessing_async.py`*
131 |
132 | ***
133 |
134 | * Using `subprocess` module to create non-self-defined subprocesses
135 |
136 | ```python
137 | import subprocess
138 |
139 | print('$ nslookup www.python.org')
140 | r = subprocess.call(['nslookup', 'www.python.org'])
141 | # 相当于在command line中输入 nslookup www.python.org
142 | print('Exit code:', r)
143 | print()
144 |
145 | # Output:
146 | # $ nslookup www.python.org
147 | # Server: 137.99.203.20
148 | # Address: 137.99.203.20#53
149 | #
150 | # Non-authoritative answer:
151 | # www.python.org canonical name = python.map.fastly.net.
152 | # Name: python.map.fastly.net
153 | # Address: 151.101.208.223
154 | #
155 | # Exit code: 0
156 |
157 | # Non-self-defined subprocesses might need input
158 | print('$ nslookup')
159 | # 相当于在command line中输入 nslookup
160 | p = subprocess.Popen(
161 | ['nslookup'],
162 | stdin=subprocess.PIPE,
163 | stdout=subprocess.PIPE,
164 | stderr=subprocess.PIPE
165 | )
166 | output, _ = p.communicate(b'set q=mx\npython.org\nexit\n')
167 | # 相当于再在command line中输入
168 | # set q=mx
169 | # python.org
170 | # exit
171 | print(output.decode('utf-8'))
172 | print('Exit code:', p.returncode)
173 | print()
174 |
175 | # Output:
176 | # $ nslookup
177 | # Server: 137.99.203.20
178 | # Address: 137.99.203.20#53
179 | #
180 | # Non-authoritative answer:
181 | # python.org mail exchanger = 50 mail.python.org
182 | #
183 | # Authoratative answers can be found from:
184 | #
185 | #
186 | # Exit code: 0
187 | ```
188 |
189 |
190 |
191 | ## Communication between Processes
192 |
193 | * Through a pipe, creating two `multiprocessing.connection.Connection` objects on both ends of the pipe
194 |
195 | ```python
196 | from multiprocessing import Pipe, Process
197 | from multiprocessing.connection import Connection
198 |
199 |
200 | def write_process(conn: Connection) -> None:
201 | """
202 | Process to write data to pipe through the given connection.
203 | :param conn: Connection
204 | :return: None
205 | """
206 | print('Child process writing to the pipe...')
207 | conn.send([42, None, 'Hello'])
208 | conn.close()
209 |
210 |
211 | parent_conn, child_conn = Pipe()
212 |
213 | p = Process(target=write_process, args=(child_conn,))
214 | p.start()
215 |
216 | print(f'Parent process reading from pipe... {parent_conn.recv()}')
217 |
218 | p.join()
219 |
220 | # Output:
221 | # Child process writing to the pipe...
222 | # Parent process reading from pipe... [42, None, 'Hello']
223 | ```
224 |
225 | * Through `multiprocessing.Queue`
226 |
227 | ```python
228 | import random
229 | import time
230 | from multiprocessing import Process, Queue
231 | from typing import List
232 |
233 |
234 | def producer_process(q: Queue, urls: List[str]) -> None:
235 | """
236 | Producer process to write data to the given message queue.
237 | :param q: Queue
238 | :param urls: list[str]
239 | :return: None
240 | """
241 | print('Producer process starts writing...')
242 | for url in urls:
243 | q.put(url)
244 | print(f'[Producer Process] Put {url} to the queue')
245 | time.sleep(random.random())
246 |
247 |
248 | def consumer_process(q: Queue) -> None:
249 | """
250 | Consumer process to get data from the given message queue.
251 | :param q: Queue
252 | :return: None
253 | """
254 | print('Consumer process starts reading...')
255 | while True:
256 | url = q.get(block=True)
257 | print(f'[Consumer Process] Get {url} from the queue')
258 |
259 |
260 | q = Queue()
261 | producer1 = Process(target=producer_process, args=(q, ['url1', 'url2', 'url3']))
262 | producer2 = Process(target=producer_process, args=(q, ['url4', 'url5']))
263 | consumer = Process(target=consumer_process, args=(q,))
264 |
265 | producer1.start()
266 | producer2.start()
267 | consumer.start()
268 |
269 | producer1.join()
270 | producer2.join()
271 |
272 | # Since there is an infinite loop in consumer_process, we need to manually force
273 | # it to stop.
274 | time.sleep(3) # Make sure the consumer has finished consuming the values
275 | consumer.terminate()
276 |
277 | # Output:
278 | # Producer process starts writing...
279 | # [Producer Process] Put url1 to the queue
280 | # Producer process starts writing...
281 | # [Producer Process] Put url4 to the queue
282 | # Consumer process starts reading...
283 | # [Consumer Process] Get url1 from the queue
284 | # [Consumer Process] Get url4 from the queue
285 | # [Producer Process] Put url5 to the queue
286 | # [Consumer Process] Get url5 from the queue
287 | # [Producer Process] Put url2 to the queue
288 | # [Consumer Process] Get url2 from the queue
289 | # [Producer Process] Put url3 to the queue
290 | # [Consumer Process] Get url3 from the queue
291 | ```
292 | * Don't make too many trips back and forth
293 |
294 | Instead, do significant work in one trip
295 |
296 | * Don't send or receive a lot of data at one time
297 |
298 |
--------------------------------------------------------------------------------
/Multi-processing and Multi-threading in Python/Multi-processing/distributed_processing_server.py:
--------------------------------------------------------------------------------
1 | #!usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Distributed processing: distribute multiple processes to multiple machines.
6 |
7 | Task server module.
8 | """
9 |
10 | __author__ = 'Ziang Lu'
11 |
12 | import random
13 | from multiprocessing import Queue
14 | from multiprocessing.managers import BaseManager
15 |
16 | # 创建发送任务的queue和接受结果的queue
17 | task_queue = Queue(maxsize=5)
18 | result_queue = Queue(maxsize=5)
19 |
20 |
21 | class ServerQueueManager(BaseManager):
22 | pass
23 |
24 |
25 | # 给ServerQueueManager注册两个函数来分别返回两个queue
26 | ServerQueueManager.register('get_task_queue', callable=lambda: task_queue)
27 | ServerQueueManager.register('get_result_queue', callable=lambda: result_queue)
28 |
29 |
30 | ##### SERVER-SIDE #####
31 |
32 | # 创建manager, 并绑定端口5000, 设置authkey "abc"
33 | server_manager = ServerQueueManager(address=('', 5000), authkey=b'abc')
34 | # 启动manager
35 | server_manager.start()
36 | print('Server manager started.')
37 |
38 | # 通过ServerQueueManager封装来获取task_queue和result_queue
39 | task_q = server_manager.get_task_queue() # 本质上是个proxy
40 | result_q = server_manager.get_result_queue() # 本质上是个proxy
41 |
42 | # 向task_q设置任务
43 | for _ in range(10):
44 | n = random.randint(0, 10000)
45 | print(f'Put task {n}...')
46 | task_q.put(n)
47 |
48 | # Output:
49 | # Server manager started.
50 | # Put task 8739...
51 | # Put task 5790...
52 | # Put task 474...
53 | # Put task 8122...
54 | # Put task 4101...
55 | # Put task 8863...
56 | # Put task 3944...
57 | # Put task 7217...
58 | # Put task 6542...
59 | # Put task 2097...
60 |
61 |
62 | # 从result_q读取任务结果
63 | print('Getting results...')
64 | for _ in range(10):
65 | # Will block here and wait for getting results
66 | r = result_q.get(timeout=10)
67 | print(f'Result: {r}')
68 |
69 | # 关闭manager
70 | server_manager.shutdown()
71 | print('Server manager exited.')
72 |
73 | # Output:
74 | # Getting results...
75 | # Result: 8739 * 8739 = 76370121
76 | # Result: 5790 * 5790 = 33524100
77 | # Result: 474 * 474 = 224676
78 | # Result: 8122 * 8122 = 65966884
79 | # Result: 4101 * 4101 = 16818201
80 | # Result: 8863 * 8863 = 78552769
81 | # Result: 3944 * 3944 = 15555136
82 | # Result: 7217 * 7217 = 52085089
83 | # Result: 6542 * 6542 = 42797764
84 | # Result: 2097 * 2097 = 4397409
85 | # Server manager exited.
86 |
--------------------------------------------------------------------------------
/Multi-processing and Multi-threading in Python/Multi-processing/distributed_processing_worker.py:
--------------------------------------------------------------------------------
1 | #!usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Distributed processing: distribute multiple processes to multiple machines.
6 |
7 | Task consumer module.
8 | """
9 |
10 | __author__ = 'Ziang Lu'
11 |
12 | import time
13 | from multiprocessing import Queue
14 | from multiprocessing.managers import BaseManager
15 |
16 |
17 | class WorkerQueueManager(BaseManager):
18 | pass
19 |
20 |
21 | # 由于WorkerQueueManager只从网络上获取queue, 所以注册时只提供名字
22 | WorkerQueueManager.register('get_task_queue')
23 | WorkerQueueManager.register('get_result_queue')
24 |
25 | ##### WORKER-SIDE #####
26 |
27 | server_addr = '127.0.0.1' # localhost
28 | # 创建manager, port和authkey注意与manager.py中保持一致
29 | worker_manager = WorkerQueueManager(
30 | address=(server_addr, 5000), authkey=b'abc'
31 | )
32 | # 连接至服务器
33 | print(f'Connecting to server {server_addr}...')
34 | worker_manager.connect()
35 | print('Worker started.')
36 |
37 | # 通过WorkerQueueManager封装来获取task_queue和result_queue
38 | task_q = worker_manager.get_task_queue() # 本质上是个proxy
39 | result_q = worker_manager.get_result_queue() # 本质上是个proxy
40 |
41 | # 从task_q获取任务, 执行任务, 并把结果写入result_q
42 | for _ in range(10):
43 | try:
44 | n = task_q.get(timeout=1)
45 | print(f'Calculating {n} * {n}...')
46 | result = f'{n} * {n} = {n * n}'
47 | time.sleep(1)
48 | result_q.put(result)
49 | except Queue.Empty:
50 | print('Task queue is empty.')
51 | print('Worker exits.')
52 |
53 |
54 | # Output:
55 | # Connecting to server 127.0.0.1...
56 | # Worker started.
57 | # Calculating 8739 * 8739...
58 | # Calculating 5790 * 5790...
59 | # Calculating 474 * 474...
60 | # Calculating 8122 * 8122...
61 | # Calculating 4101 * 4101...
62 | # Calculating 8863 * 8863...
63 | # Calculating 3944 * 3944...
64 | # Calculating 7217 * 7217...
65 | # Calculating 6542 * 6542...
66 | # Calculating 2097 * 2097...
67 | # Worker exits.
68 |
--------------------------------------------------------------------------------
/Multi-processing and Multi-threading in Python/Multi-processing/multiprocessing_async.py:
--------------------------------------------------------------------------------
1 | #!usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | We can assign asynchronous tasks into a process pool.
6 |
7 | This can implemented in two ways:
8 | - one with "concurrent.futures" module
9 | - one with "multiprocessing" module
10 | """
11 |
12 | __author__ = 'Ziang Lu'
13 |
14 | import concurrent.futures as cf
15 | import os
16 | import random
17 | import time
18 | from multiprocessing import Pool
19 |
20 |
21 | def long_time_task(name: str) -> float:
22 | """
23 | Dummy long task to be run within a process.
24 | :param name: str
25 | :return: float
26 | """
27 | print(f"Running task '{name}' ({os.getpid()})...")
28 | start = time.time()
29 | time.sleep(random.random() * 3)
30 | end = time.time()
31 | time_elapsed = end - start
32 | print(f"Task '{name}' runs {time_elapsed:.2f} seconds.")
33 | return time_elapsed
34 |
35 |
36 | def demo1():
37 | # With "concurrent.futures" module
38 | print(f'Parent process {os.getpid()}')
39 | with cf.ProcessPoolExecutor(max_workers=4) as pool: # 开启一个4个进程的进程池
40 | start = time.time()
41 | # 在进程池中执行多个任务, 但是在主进程中是async的
42 | futures = [pool.submit(long_time_task, f'Task-{i}') for i in range(5)] # Will NOT block here
43 |
44 | # Since submit() method is asynchronous (non-blocking), by now the tasks
45 | # in the process pool are still executing, but in this main process, we
46 | # have successfully proceeded to here.
47 | total_running_time = 0
48 | for future in cf.as_completed(futures):
49 | total_running_time += future.result()
50 |
51 | print(f'Theoretical total running time: {total_running_time:.2f} '
52 | f'seconds.')
53 | end = time.time()
54 | print(f'Actual running time: {end - start:.2f} seconds.')
55 | print('All subprocesses done.')
56 |
57 | # Output:
58 | # Parent process 41585
59 | # Running task 'Task-0' (41586)...
60 | # Running task 'Task-1' (41587)...
61 | # Running task 'Task-2' (41588)...
62 | # Running task 'Task-3' (41589)...
63 | # Task 'Task-3' runs 0.29 seconds.
64 | # Running task 'Task-4' (41589)...
65 | # Task 'Task-1' runs 0.43 seconds.
66 | # Task 'Task-2' runs 0.46 seconds.
67 | # Task 'Task-4' runs 0.79 seconds.
68 | # Task 'Task-0' runs 1.25 seconds.
69 | # Theoretical total running time: 3.21 seconds.
70 | # Actual running time: 1.26 seconds.
71 | # All subprocesses done.
72 |
73 |
74 | def demo2():
75 | # With "multiprocessing" module
76 | with Pool(4) as pool: # 开启一个4个进程的进程池
77 | start = time.time()
78 | # 在进程池中执行多个任务, 但是在主进程中是async的
79 | results = [
80 | pool.apply_async(func=long_time_task, args=(f'Task-{i}',))
81 | for i in range(5)
82 | ] # Will NOT block here
83 | pool.close() # 调用close()之后就不能再添加新的任务了
84 |
85 | # Since apply_async() method is asynchronous (non-blocking), by now the
86 | # tasks in the process pool are still executing, but in this main
87 | # process, we have successfully proceeded to here.
88 | total_running_time = 0
89 | for result in results: # AsyncResult
90 | total_running_time += result.get(timeout=10) # Returns the result when it arrives
91 |
92 | print(f'Theoretical total running time: {total_running_time:.2f} '
93 | f'seconds.')
94 | end = time.time()
95 | print(f'Actual running time: {end - start:.2f} seconds.')
96 | print('All subprocesses done.')
97 |
98 | # Output:
99 | # Parent process 10161
100 | # Running task 'Task-0' (10162)...
101 | # Running task 'Task-1' (10163)...
102 | # Running task 'Task-2' (10164)...
103 | # Running task 'Task-3' (10165)...
104 | # Task 'Task-2' runs 1.08 seconds.
105 | # Running task 'Task-4' (10164)...
106 | # Task 'Task-3' runs 1.43 seconds.
107 | # Task 'Task-0' runs 2.69 seconds.
108 | # Task 'Task-1' runs 2.72 seconds.
109 | # Task 'Task-4' runs 2.07 seconds.
110 | # Theoretical total running time: 10.00 seconds.
111 | # Actual running time: 3.18 seconds.
112 | # All subprocesses done.
113 |
--------------------------------------------------------------------------------
/Multi-processing and Multi-threading in Python/Multi-threading/Locks and Sync Blocking/sync_blocking.py:
--------------------------------------------------------------------------------
1 | #!usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Test module to implement synchronous blocking to address race condition among
6 | threads, using threading.Lock and
7 | threading.Semaphore/threading.BoundedSemaphore.
8 | """
9 |
10 | __author__ = 'Ziang Lu'
11 |
12 | import time
13 | from threading import BoundedSemaphore, Lock, Semaphore, Thread, current_thread
14 |
15 | balance = 0
16 |
17 | # Lock
18 | lock = Lock()
19 |
20 |
21 | def thread_func(n: int) -> None:
22 | """
23 | Dummy function to be run within a thread.
24 | :param n: int
25 | :return: None
26 | """
27 | for _ in range(10000000):
28 | # Note that Lock objects can be used in a traditional way, i.e., via
29 | # acquire() and release() methods, but it can also simply be used as a
30 | # context manager, as a syntax sugar
31 | with lock:
32 | change_balance(n)
33 |
34 |
35 | def change_balance(n: int) -> None:
36 | """
37 | Changes the balance.
38 | :param n: int
39 | :return: None
40 | """
41 | global balance
42 | balance += n
43 | balance -= n
44 | # 先存后取, 效果应该为无变化
45 |
46 |
47 | th1 = Thread(target=thread_func, args=(5,))
48 | th2 = Thread(target=thread_func, args=(8,))
49 | th1.start()
50 | th2.start()
51 | th1.join()
52 | th2.join()
53 | print(balance)
54 |
55 |
56 | # Output:
57 | # 0
58 |
59 |
60 | # Semaphore
61 | # 管理一个内置的计数器, 每当调用acquire()时-1, 调用release()时+1
62 | # 计数器不能小于0; 当计数器为0时, acquire()将阻塞线程至同步锁定状态, 直到其他线程调用
63 | # release()
64 |
65 | # 注意: 同时acquire semaphore的线程仍然可能会有race condition
66 |
67 | # BoundedSemaphore
68 | # 在Semaphore的基础上, 不允许计数器超过initial value (设置上限)
69 |
70 | # A bounded semaphore with initial value 2
71 | bounded_sema = BoundedSemaphore(value=2)
72 |
73 |
74 | def func() -> None:
75 | """
76 | Dummy function.
77 | :return: None
78 | """
79 | th_name = current_thread().name
80 | # 请求Semaphore, 成功后计数器-1
81 | print(f'{th_name} acquiring semaphore...')
82 | # Note that BoundedSemaphore objects can be used in a traditional way, i.e.,
83 | # via acquire() and release() methods, but it can also simply be used as a
84 | # context manager, as a syntax sugar
85 | with bounded_sema: # 释放Semaphore的时候, 计数器+1
86 | print(f'{th_name} gets semaphore')
87 | time.sleep(4)
88 |
89 |
90 | threads = [Thread(target=func) for _ in range(4)]
91 | for th in threads:
92 | th.start()
93 | for th in threads:
94 | th.join()
95 |
96 |
97 | # Output:
98 | # Thread-3 acquiring semaphore...
99 | # Thread-3 gets semaphore
100 | # Thread-4 acquiring semaphore...
101 | # Thread-4 gets semaphore
102 | # Thread-5 acquiring semaphore...
103 | # Thread-6 acquiring semaphore... # Will block here for 4 seconds, waiting for the semaphore
104 | # Thread-5 gets semaphore
105 | # Thread-6 gets semaphore
106 |
--------------------------------------------------------------------------------
/Multi-processing and Multi-threading in Python/Multi-threading/Locks and Sync Blocking/wait_blocking.py:
--------------------------------------------------------------------------------
1 | #!usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Test module to implement wait blocking to address race condition, using
6 | threading.Condition and threading.Event.
7 | """
8 |
9 | __author__ = 'Ziang Lu'
10 |
11 | import time
12 | from threading import Condition, Event, Thread, current_thread
13 |
14 | # Condition
15 |
16 | product = None # 商品
17 | condition = Condition()
18 |
19 |
20 | def producer() -> None:
21 | while True:
22 | if condition.acquire():
23 | global product
24 | if not product:
25 | # 生产商品
26 | print('Producing something...')
27 | product = 'anything'
28 | condition.notify()
29 |
30 | condition.wait()
31 | time.sleep(2)
32 |
33 |
34 | def consumer() -> None:
35 | while True:
36 | if condition.acquire():
37 | global product
38 | if product:
39 | # 消耗商品
40 | print('Consuming something...')
41 | product = None
42 | condition.notify()
43 |
44 | condition.wait()
45 | time.sleep(2)
46 |
47 |
48 | prod_thread = Thread(target=producer)
49 | cons_thread = Thread(target=consumer)
50 | prod_thread.start()
51 | cons_thread.start()
52 |
53 |
54 | # Output:
55 | # Producing something...
56 | # Consuming something...
57 | # Producing something...
58 | # Consuming something...
59 | # ...
60 |
--------------------------------------------------------------------------------
/Multi-processing and Multi-threading in Python/Multi-threading/Multi-threading in Python.md:
--------------------------------------------------------------------------------
1 | # Multi-threading in Python
2 |
3 | ## Basic Implementations
4 |
5 | * Using `threading` module
6 |
7 | * Normal implementation
8 |
9 | ```python
10 | import time
11 | from threading import Thread, current_thread
12 |
13 |
14 | def loop() -> None:
15 | """
16 | Loop to be run within a thread.
17 | :return: None
18 | """
19 | th_name = current_thread().name
20 | print(f'Running thread {th_name}...')
21 | n = 1
22 | while n < 5:
23 | print(f'thread {th_name} >>> {n}')
24 | time.sleep(1)
25 | n += 1
26 | print(f'Thread {th_name} ends.')
27 |
28 |
29 | th_name = current_thread().name
30 | print(f'Running thread {th_name}...')
31 | th = Thread(target=loop, name='LoopThread')
32 | th.start()
33 | th.join()
34 | print(f'Thread {th_name} ends.')
35 | print()
36 |
37 | # Output:
38 | # Running thread MainThread...
39 | # Running thread LoopThread...
40 | # thread LoopThread >>> 1
41 | # thread LoopThread >>> 2
42 | # thread LoopThread >>> 3
43 | # thread LoopThread >>> 4
44 | # Thread LoopThread ends.
45 | # Thread MainThread ends.
46 |
47 |
48 | # 注意:
49 | # Python的thread虽然是真正的thread, 但解释器执行代码时, 有一个GIL锁 (Global Interpreter Lock)
50 | # 任何Python thread执行前, 必须先获得GIL, 然后, 每执行100条字节码, 解释器就自动释放GIL锁, 让别的thread有机会执行
51 | # (Python 2.x)
52 | # 所以, multi-threading在Python中只能交替进行
53 | # (即使100个thread跑在100核CPU上, 也只能用到1个核 => 不可能通过multi-threading实现parallelism)
54 | # 但由于每个process有各自独立的GIL, 可以通过multi-processing实现concurrency
55 | ```
56 |
57 | * Extending `threading.Thread` class, and override `run()` method
58 |
59 | ```python
60 | from threading import Thread
61 |
62 | import requests
63 | from bs4 import BeautifulSoup
64 |
65 |
66 | class MyThread(Thread):
67 | """
68 | My inherited thread class.
69 | """
70 | __slots__ = ['_page_num']
71 |
72 | _BASE_URL = 'https://movie.douban.com/top250?start={}&filter='
73 |
74 | def __init__(self, page_num: int):
75 | """
76 | Constructor with parameter.
77 | :param page_num: int
78 | """
79 | super().__init__()
80 | self._page_num = page_num
81 |
82 | def run(self):
83 | # Note that run() method is automatically called when start() method is
84 | # called
85 |
86 | url = self._BASE_URL.format(self._page_num * 25)
87 | r = requests.get(url)
88 | soup = BeautifulSoup(r.text, 'lxml')
89 | title_tags = soup.find('ol', class_='grid_view').find_all('li')
90 | for title_tag in title_tags:
91 | title = title_tag.find('span', class_='title').text
92 | print(title)
93 |
94 |
95 | for page_num in range(10):
96 | th = MyThread(page_num)
97 | th.start()
98 | ```
99 |
100 | * **Multi-threading + Async (Thread [Coroutine])**
101 |
102 | -> 把coroutine包在thread中
103 |
104 | *创建一个thread pool, 在其中放入async的task (coroutine), 参见`multithreading_async.py`*
105 |
106 |
107 |
108 | ## Python Thread的wait和notify
109 |
110 | ### Condition (条件变量)
111 |
112 | 通常与一个锁关联, 需要在多个Condition中共享一个锁时, 可以传递一个Lock/RLock实例给constructor, 否则它将自己生成一个RLock实例
113 |
114 | **相当于除了Lock带有的锁定池外, Condition还自带一个等待池**
115 |
116 | **构造方法:**
117 |
118 | `Condition(lock=None)`
119 |
120 | **实例方法:**
121 |
122 | - `acquire(*args)` / `release()`
123 |
124 | 调用关联的锁的相应方法
125 |
126 | - `wait(timeout=None)` *(使用前当前thread必须已获得锁定, 否则将抛出异常)*
127 |
128 | 使当前thread进入Condition的等待池等待通知, 并释放锁
129 |
130 | - `notify(n=1)` *(使用前当前thread必须已获得锁定, 否则将抛出异常)*
131 |
132 | 从Condition的等待池中随机挑选一个thread来通知, 收到通知的thread将自动调用acquire()来尝试获得锁定 (进入锁定池), 但当前thread不会释放锁
133 |
134 | - `notify_all()` *(使用前当前thread必须已获得锁定, 否则将抛出异常)*
135 |
136 | 通知Condition的等待池中的全部thread, 收到通知的全部thread将自动调用acquire()来尝试获得锁定 (进入锁定池), 但当前thread不会释放锁
137 |
138 |
139 |
140 | #### 万恶的Python GIL (Global Interpreter Lock)
141 |
142 | **在Python中, 某个thread想要执行, 必须先拿到GIL.**
143 |
144 | *(我们可以把GIL看作是"通行证", 并且在一个Python process中, GIL只有一个. 拿不到通行证的thread, 就不允许进入CPU执行.)*
145 |
146 | => **由于GIL的存在, Python里一个process同一时间永远只能执行一个thread (即拿到GIL的thread才能执行)**; 而每次释放GIL, thread进行锁竞争、切换thread, 会消耗资源, 这就是为什么**Python即使是在多核CPU上, multi-threading的效率也并不高**
147 |
148 |
149 |
150 | 在Python multi-threading下, 每个thread的执行方式:
151 |
152 | 1. 获取process GIL
153 |
154 | 2. 执行代码直到sleep或者是Python虚拟机将其挂起
155 |
156 | 3. 释放process GIL
157 |
158 | - 遇到IO操作
159 |
160 | => **对于IO密集型程序, 可以在thread-A等待时, 自动切换到thread-B, 不浪费等待时间, 从而提升程序执行效率**
161 |
162 | => **Python multi-threading对IO密集型代码比较友好**
163 |
164 | - Python 2.x中, ticks计数达到100
165 |
166 | => 对于CPU计算密集型程序, 由于ticks会很快达到100, 然后进行释放GIL, thread进行锁竞争、切换thread, 导致资源消耗严重
167 |
168 | - Python 3.x中, 改为计时器达到某个阈值
169 |
170 | => 对于CPU计算密集型程序稍微友好了一些, 但依然没有解决根本问题
171 |
172 | => **Python multi-threading对CPU计算密集型代码不友好**
173 |
174 |
175 |
176 | **结论: 多核下, 想做concurrent提升效率, 比较通用的方法是使用multi-processing, 能够有效提高执行效率** (每个process有各自独立的GIL, 互不干扰, 这样就可以真正意义上的parallel执行.)
177 |
178 |
--------------------------------------------------------------------------------
/Multi-processing and Multi-threading in Python/Multi-threading/Race Condition Demo by Raymond Hettinger/comm_via_atomic_message_queue.py:
--------------------------------------------------------------------------------
1 | #!usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Simple demo of solution of race conditions with (atomic) message queue.
6 | Note that the race conditions in this demo are also amplified by fuzzing
7 | technique
8 |
9 | In order to be away with race conditions, we need to
10 | 1. Ensure an explicit ordering of the operations (on the shared resources)
11 | All operations (on the shared resources) must be executed in the same order
12 | they are received.
13 | => To ensure this, we simply put the operations (on one shared resource) in a
14 | single thread.
15 | 2. Restrict access to the shared resource
16 | Only one operation can access the shared resource at the same time. During
17 | the period of access, no other operations can read or change its value.
18 |
19 | Specifically for solution with (atomic) message queue,
20 | 1. Each shared resource shall be accessed in exactly its own thread.
21 | 2. All communications with that thread shall be done using an atomic message
22 | queue.
23 | """
24 |
25 | import random
26 | import time
27 | from queue import Queue
28 | from threading import Thread
29 |
30 | ##### Fuzzing technique #####
31 |
32 | FUZZ = False
33 |
34 |
35 | def fuzz() -> None:
36 | """
37 | Fuzzes the program for a random amount of time, if instructed.
38 | :return: None
39 | """
40 | if FUZZ:
41 | time.sleep(random.random())
42 |
43 |
44 | ##### Daemon thread for print() access & Atomic message queue for print() #####
45 |
46 | # Note that the built-in print() function is a "global" resource, and thus could
47 | # lead to race condition
48 | # Therefore, as instructed above, accessing print() function should be handled
49 | # in exactly its own thread, which should be a daemon thread. (=> 1)
50 |
51 | # All communications with the print()-access daemon thread shall be done using
52 | # an atomic message queue. (=> 2)
53 | print_queue = Queue() # Atomic message queue used for the print()-access daemon thread
54 |
55 |
56 | def print_manager() -> None:
57 | """
58 | Daemon thread function that uses an atomic message queue to communicate with
59 | the external world, and has exclusive right to access print() function.
60 | :return: None
61 | """
62 | while True:
63 | fuzz()
64 | stuff_to_print = print_queue.get()
65 | for line in stuff_to_print:
66 | fuzz()
67 | print(line)
68 | fuzz()
69 | # Mark the task as done
70 | print_queue.task_done()
71 |
72 |
73 | # Create and start the print()-access daemon thread
74 | print_daemon_thread = Thread(
75 | target=print_manager, name='print()-access Daemon Thread'
76 | )
77 | print_daemon_thread.daemon = True
78 | print_daemon_thread.start()
79 |
80 |
81 | ##### Daemon thread for "counter" & Atomic message queue for "counter" #####
82 |
83 | counter = 0
84 | # As instructed above, accessing the "counter" global variable should be handled
85 | # in exactly its own thread, which should be a daemon thread. (=> 1)
86 |
87 | # All communications with the "counter"-access daemon thread shall be done using
88 | # an atomic message queue. (=> 2)
89 | # Atomic message queue used for the "counter"-access daemon thread
90 | counter_queue = Queue()
91 |
92 |
93 | def counter_manager() -> None:
94 | """
95 | Daemon thread function that uses an atomic message queue to communicate with
96 | the external world, and has exclusive right to access the "counter" global
97 | variable.
98 | :return: None
99 | """
100 | while True:
101 | global counter
102 | fuzz()
103 | old_val = counter
104 | fuzz()
105 | increment = counter_queue.get()
106 | fuzz()
107 | counter = old_val + increment
108 | fuzz()
109 | # Send a message to the print()-access atomic message queue in order to
110 | # print the "counter" value
111 | print_queue.put([f'Counter value: {counter}', '----------'])
112 | fuzz()
113 | # Mark the task as done
114 | counter_queue.task_done()
115 |
116 |
117 | # Create and start the "counter"-access daemon thread
118 | counter_daemon_thread = Thread(
119 | target=counter_manager, name='Counter-access Daemon Thread'
120 | )
121 | counter_daemon_thread.daemon = True
122 | counter_daemon_thread.start()
123 |
124 |
125 | ##### Actual workers #####
126 |
127 |
128 | def worker() -> None:
129 | """
130 | Thread function that increments "counter" by 1.
131 | This is done by sending a message to the "counter"-access atomic message
132 | queue.
133 | :return: None
134 | """
135 | fuzz()
136 | counter_queue.put(1)
137 |
138 |
139 | print_queue.put(['Starting up'])
140 |
141 | # Create and start 10 worker threads
142 | worker_threads = []
143 | for _ in range(10):
144 | worker_thread = Thread(target=worker)
145 | worker_threads.append(worker_thread)
146 | worker_thread.start()
147 | fuzz()
148 | # Join the 10 worker threads
149 | for worker_thread in worker_threads:
150 | worker_thread.join()
151 | fuzz()
152 | # By now, it is guaranteed that 10 messages have been sent to the
153 | # "counter"-access atomic message queue, but the tasks haven't necesserily been
154 | # done yet.
155 |
156 | # Note that since the "counter"-access queue is a daemon thread and never ends,
157 | # we cannot join it
158 | # Instead, we can join the atomic message queue itself, which waits until all
159 | # the tasks have been marked as done.
160 | counter_queue.join()
161 |
162 | print_queue.put(['Finishing up'])
163 | print_queue.join() # Same reason as above
164 |
165 | # Output:
166 | # Starting up
167 | # Counter value: 1
168 | # ----------
169 | # Counter value: 2
170 | # ----------
171 | # Counter value: 3
172 | # ----------
173 | # Counter value: 4
174 | # ----------
175 | # Counter value: 5
176 | # ----------
177 | # Counter value: 6
178 | # ----------
179 | # Counter value: 7
180 | # ----------
181 | # Counter value: 8
182 | # ----------
183 | # Counter value: 9
184 | # ----------
185 | # Counter value: 10
186 | # ----------
187 | # Finishing up
188 |
--------------------------------------------------------------------------------
/Multi-processing and Multi-threading in Python/Multi-threading/Race Condition Demo by Raymond Hettinger/comm_via_locks.py:
--------------------------------------------------------------------------------
1 | #!usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Simple demo of solution of race conditions with locks.
6 | Note that the race conditions in this demo are also amplified by fuzzing
7 | technique
8 |
9 | In order to be away with race conditions, we need to
10 | 1. Ensure an explicit ordering of the operations (on the shared resources)
11 | All operations (on the shared resources) must be executed in the same order
12 | they are received.
13 | 2. Restrict access to the shared resource
14 | Only one operation can access the shared resource at the same time. During
15 | the period of access, no other operations can read or change its value.
16 |
17 | Specifically for solution with locks,
18 | 2. All accesses to the shared resource shall be done using its own lock.
19 | """
20 |
21 | import random
22 | import time
23 | from threading import Condition, Thread
24 |
25 | ##### Fuzzing technique #####
26 |
27 | FUZZ = False
28 |
29 |
30 | def fuzz() -> None:
31 | """
32 | Fuzzes the program for a random amount of time, if instructed.
33 | :return: None
34 | """
35 | if fuzz:
36 | time.sleep(random.random())
37 |
38 |
39 | ##### Locks for print()-access & "counter"-access #####
40 |
41 | # All accesses to the shared resource shall be done using its own lock. (=> 2)
42 | print_lock = Condition() # Lock for print()-access
43 |
44 | counter = 0
45 |
46 | counter_lock = Condition() # Lock for "counter"-access
47 |
48 |
49 | def worker() -> None:
50 | """
51 | Thread function that increments "counter" by 1.
52 | :return: None
53 | """
54 | global counter
55 | # Lock on the "counter"-access lock
56 | with counter_lock:
57 | fuzz()
58 | old_val = counter
59 | fuzz()
60 | counter = old_val + 1
61 | # Lock on the print()-access lock
62 | with print_lock:
63 | fuzz()
64 | print(f'The counter value is {counter}')
65 | fuzz()
66 | print('----------')
67 |
68 |
69 | # Lock on the print()-access lock
70 | with print_lock:
71 | print('Starting up')
72 |
73 | # Create and start 10 worker threads
74 | worker_threads = []
75 | for _ in range(10):
76 | worker_thread = Thread(target=worker)
77 | worker_threads.append(worker_thread)
78 | worker_thread.start()
79 | fuzz()
80 | # Join the 10 worker threads
81 | for worker_thread in worker_threads:
82 | worker_thread.join()
83 | fuzz()
84 |
85 | # Lock on the print()-access lock
86 | with print_lock:
87 | print('Finishing up')
88 |
89 | # Output:
90 | # Starting up
91 | # The counter value is 1
92 | # ----------
93 | # The counter value is 2
94 | # ----------
95 | # The counter value is 3
96 | # ----------
97 | # The counter value is 4
98 | # ----------
99 | # The counter value is 5
100 | # ----------
101 | # The counter value is 6
102 | # ----------
103 | # The counter value is 7
104 | # ----------
105 | # The counter value is 8
106 | # ----------
107 | # The counter value is 9
108 | # ----------
109 | # The counter value is 10
110 | # ----------
111 | # Finishing up
112 |
--------------------------------------------------------------------------------
/Multi-processing and Multi-threading in Python/Multi-threading/Race Condition Demo by Raymond Hettinger/note:
--------------------------------------------------------------------------------
1 | This example is summarized from a talk on PyCon 2016 by Raymond Hettinger:
2 | https://www.youtube.com/watch?v=Bv25Dwe84g0&index=4&list=PLZdWBibC1pBRc5317FXPYMkxVPmnHGz7f
3 |
--------------------------------------------------------------------------------
/Multi-processing and Multi-threading in Python/Multi-threading/Race Condition Demo by Raymond Hettinger/race_condition.py:
--------------------------------------------------------------------------------
1 | #!usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Simple demo of race condition, amplified by fuzzing technique.
6 | """
7 |
8 | import random
9 | import time
10 | from threading import Thread
11 |
12 | # Fizzing is a technique for amplifying race condition to make them more
13 | # visible.
14 | # Basically, define a fuzz() function, which simply sleeps a random amount of
15 | # time if instructed, and then call the fuzz() function before each operation
16 |
17 | # Fuzzing setup
18 |
19 | FUZZ = True
20 |
21 |
22 | def fuzz() -> None:
23 | """
24 | Fuzzes the program for a random amount of time, if instructed.
25 | :return: None
26 | """
27 | if FUZZ:
28 | time.sleep(random.random())
29 |
30 |
31 | # Shared global variable
32 | counter = 0
33 |
34 |
35 | def worker() -> None:
36 | """
37 | :return: None
38 | """
39 | global counter
40 | fuzz()
41 | old_val = counter # Read from the global variable
42 | fuzz()
43 | counter = old_val + 1 # Write to the global variable
44 | # Whenever there is read from/write to global variables, there could be race
45 | # condition.
46 | # To amplify this race condition to make it more visible, we utilize fuzzing
47 | # technique as mentioned above.
48 | fuzz()
49 | # Note that the built-in print() function is also a "global" resource, and
50 | # thus could lead to race condition as well
51 | print(f'The counter is {counter}')
52 | fuzz()
53 | print('----------')
54 |
55 |
56 | print('Starting up')
57 | for _ in range(10):
58 | Thread(target=worker).start()
59 | fuzz()
60 | print('Finishing up')
61 |
--------------------------------------------------------------------------------
/Multi-processing and Multi-threading in Python/Multi-threading/multithreading_async.py:
--------------------------------------------------------------------------------
1 | #!usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | We can assign asynchronous tasks into a thread pool.
6 | """
7 |
8 | __author__ = 'Ziang Lu'
9 |
10 | import concurrent.futures as cf
11 |
12 | import requests
13 |
14 | sites = [
15 | 'http://europe.wsj.com/',
16 | 'http://some-made-up-domain.com/',
17 | 'http://www.bbc.co.uk/',
18 | 'http://www.cnn.com/',
19 | 'http://www.foxnews.com/',
20 | ]
21 |
22 |
23 | def site_size(url: str) -> int:
24 | """
25 | Returns the page size in bytes of the given URL.
26 | :param url: str
27 | :return: str
28 | """
29 | response = requests.get(url)
30 | return len(response.content)
31 |
32 |
33 | # Create a thread pool with 10 threads
34 | with cf.ThreadPoolExecutor(max_workers=10) as pool:
35 | # Submit tasks for execution
36 | future_to_url = {pool.submit(site_size, url): url for url in sites} # Will NOT block here
37 |
38 | # Since submit() method is asynchronous (non-blocking), by now the tasks in
39 | # the thread pool are still executing, but in this main thread, we have
40 | # successfully proceeded to here.
41 | # Wait until all the submitted tasks have been completed
42 | for future in cf.as_completed(future_to_url):
43 | url = future_to_url[future]
44 | try:
45 | # Get the execution result
46 | page_size = future.result()
47 | except Exception as e:
48 | print(f'{url} generated an exception: {e}')
49 | else:
50 | print(f'{url} page is {page_size} bytes.')
51 |
52 | # Output:
53 | # http://some-made-up-domain.com/ generated an exception: HTTPConnectionPool(host='some-made-up-domain.com', port=80): Max retries exceeded with url: / (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 8] nodename nor servname provided, or not known'))
54 | # http://www.foxnews.com/ page is 216594 bytes.
55 | # http://www.cnn.com/ page is 1725827 bytes.
56 | # http://europe.wsj.com/ page is 979035 bytes.
57 | # http://www.bbc.co.uk/ page is 289252 bytes.
58 |
--------------------------------------------------------------------------------
/Multi-threading in Java/AtomicIntegerDemo.java:
--------------------------------------------------------------------------------
1 | import java.util.concurrent.atomic.AtomicInteger;
2 |
3 | /**
4 | * Self-defined class that implements Runnable interface.
5 | */
6 | class Processing implements Runnable {
7 | /**
8 | * Number of processed tasks.
9 | */
10 | // private int processedCount = 0;
11 | private AtomicInteger processedCount = new AtomicInteger();
12 |
13 | /**
14 | * Accessor of processedCount.
15 | * @return processedCount
16 | */
17 | int getProcessedCount() {
18 | // return processedCount;
19 | return processedCount.get();
20 | }
21 |
22 | @Override
23 | public void run() {
24 | for (int i = 1; i < 5; ++i) {
25 | processSomeTask(i);
26 | System.out.println(Thread.currentThread().getName() + " finished processing task-" + i);
27 |
28 | // Wrong implementation:
29 | // ++processedCount;
30 | // Normal integer increment is NOT atomic, resulting in race condition and thus wrong final result.
31 |
32 | // Correct implementation: Use AtomicInteger
33 | processedCount.incrementAndGet();
34 | // AtomicInteger increment is atomic.
35 | }
36 | }
37 |
38 | /**
39 | * Private helper method to process some task.
40 | * @param i task-i
41 | */
42 | private void processSomeTask(int i) {
43 | try {
44 | Thread.sleep(i * 1000);
45 | } catch (InterruptedException e) {
46 | e.printStackTrace();
47 | }
48 | }
49 | }
50 |
51 | /**
52 | * Simple demo for using AtomicInteger for atomic operations.
53 | *
54 | * @author Ziang Lu
55 | */
56 | public class AtomicIntegerDemo {
57 |
58 | /**
59 | * Main driver.
60 | * @param args arguments from command line
61 | */
62 | public static void main(String[] args) {
63 | Processing sharedRunnable = new Processing();
64 | Thread th1 = new Thread(sharedRunnable, "Thread-1"), th2 = new Thread(sharedRunnable, "Thread-2");
65 | th1.start();
66 | th2.start();
67 | try {
68 | th1.join();
69 | th2.join();
70 | } catch (InterruptedException e) {
71 | e.printStackTrace();
72 | }
73 | System.out.println("Total processed count: " + sharedRunnable.getProcessedCount());
74 |
75 | /*
76 | * Output:
77 | * Thread-1 finished processing task-1
78 | * Thread-2 finished processing task-1
79 | * Thread-2 finished processing task-2
80 | * Thread-1 finished processing task-2
81 | * Thread-1 finished processing task-3
82 | * Thread-2 finished processing task-3
83 | * Thread-1 finished processing task-4
84 | * Thread-2 finished processing task-4
85 | * Total processed count: 8
86 | */
87 | }
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/Multi-threading in Java/Bank User Example from 15-619 Cloud Computing/BankUserMultiThreaded.java:
--------------------------------------------------------------------------------
1 | import java.util.PriorityQueue;
2 |
3 | public class BankUserMultiThreaded {
4 |
5 | /**
6 | * Balance of the bank user.
7 | */
8 | private static int balance;
9 | /**
10 | * Min-heap for the deposit/withdraw operation timestamps.
11 | *
12 | * In order to ensure an explicit ordering of the deposit/withdraw
13 | * operations, i.e., all the operations must be executed in the order they
14 | * are received:
15 | * we create a min-heap of the timestamps of the deposit/withdraw operation.
16 | */
17 | private static final PriorityQueue operations = new PriorityQueue<>();
18 |
19 | /**
20 | * Adds the given amount to the balance.
21 | * No need to do authentication in deposit() method.
22 | * @param amt amount to deposit
23 | */
24 | public static void deposit(final int amt) {
25 | // Get the timestamp of the operation, and push it to the min-heap
26 | Long timestamp = getTime();
27 | operations.offer(timestamp);
28 |
29 | // Create a new thread for the deposit operation
30 | Thread th = new Thread(new Runnable() {
31 | @Override
32 | public void run() {
33 | // 尝试获得operations的monitor
34 | acquireLock(timestamp);
35 | // 当acquireLock()方法退出时, 当前线程已成功获得operations的monitor
36 |
37 | // No need to do authentication
38 | balance += amt;
39 | System.out.println(String.format("Deposit %d to funds. Not at %d", amt, balance));
40 |
41 | // 完成operation, 并释放operations的monitor
42 | releaseLock();
43 | }
44 | }, "Thread-Deposit");
45 | th.start();
46 | }
47 |
48 | /**
49 | * Private helper method to get the current timestamp.
50 | * @return current timestamp
51 | */
52 | private static Long getTime() {
53 | return System.nanoTime();
54 | }
55 |
56 | /**
57 | * Private helper method for the given operation timestamp's thread to
58 | * acquire operations's monitor.
59 | * @param timestamp given operation timestamp
60 | */
61 | private static void acquireLock(Long timestamp) {
62 | synchronized (operations) { // 尝试获得operations的monitor
63 | // 成功获得operations的monitor后
64 |
65 | // Check whether the operation to perform is the current operation
66 | while (timestamp != operations.peek()) {
67 | try {
68 | operations.wait(); // 当前线程进入operations的等待池, 并释放operations的monitor
69 | } catch (InterruptedException ex) {
70 | System.out.println(Thread.currentThread().getName() + " interrupted");
71 | }
72 | }
73 | // 此时, 当前的操作已经是下一步执行的操作, 且当前线程已再次获得operation的monitor
74 | }
75 | }
76 |
77 | /**
78 | * Private helper method for the current operation timestamp's thread to
79 | * release operation's monitor.
80 | */
81 | private static void releaseLock() {
82 | // Remove the timestamp of the operation just performed
83 | operations.poll();
84 | synchronized (operations) {
85 | operations.notifyAll(); // 通知operations的等待池中的全部线程来竞争operations的monitor
86 | // 根据第66行, 下一步操作的线程会成功获得operations的monitor而退出acquireLock()方法, 而对于
87 | // 不是下一步操作的线程, 该线程会重新进入operations的等待池
88 | // 注意这里不能用notify(), 因为一旦唤醒的不是下一步操作的线程, 根据第66行, 该线程会重新进入operations的等待池, 即全部线
89 | // 程都在operations的等待池中, 却没有一个再次唤醒另一个线程, 会导致deadlock
90 | }
91 | }
92 |
93 | /**
94 | * Private helper method to wait 50 milliseconds.
95 | */
96 | private static void wait_50ms() {
97 | try {
98 | Thread.sleep(50);
99 | } catch (InterruptedException e) {
100 | return;
101 | }
102 | }
103 |
104 | /**
105 | * Removes the given amount from the balance.
106 | * @param amt amount to withdraw
107 | */
108 | public static void withdraw(final int amt) {
109 | // Get the timestamp of the operation, and push it to the min-heap
110 | Long timestamp = getTime();
111 | operations.offer(timestamp);
112 |
113 | // Create a new thread responsible for the withdraw operation
114 | Thread th = new Thread(new Runnable() {
115 | @Override
116 | public void run() {
117 | // 尝试获得operations的monitor
118 | acquireLock(timestamp);
119 | // 当acquireLock()方法退出时, 当前线程已成功获得operations的monitor
120 |
121 | int holdings = balance;
122 | if (!authenticate()) { // authenticate() method always takes 500 milliseconds to run.
123 | return;
124 | }
125 | if (holdings < amt) {
126 | System.out.println(String.format(
127 | "Overdraft Error: insufficient funds for this withdrawl. Balance = %d. Amt = %d", holdings, amt
128 | ));
129 | // 完成操作, 并释放operations的monitor
130 | releaseLock();
131 | return;
132 | }
133 | balance = holdings - amt;
134 | System.out.println(String.format("Withdraw %d from funds. Now at %d", amt, balance));
135 |
136 | // 完成操作, 并释放operations的monitor
137 | releaseLock();
138 | }
139 | }, "Thread-Withdraw");
140 | th.start();
141 | }
142 |
143 | /**
144 | * Private helper method to authenticate.
145 | * Due to poor optimization, this method always takes 500 milliseconds to
146 | * run.
147 | * @return whether the authentication succeeds
148 | */
149 | public static Boolean authenticate() {
150 | try {
151 | Thread.sleep(500);
152 | } catch (InterruptedException e) {
153 | System.out.println("ERROR: ABORT OPERATION, Authentication failed");
154 | return false;
155 | }
156 | return true;
157 | }
158 |
159 | public static void test_case0() {
160 | balance = 0;
161 | deposit(100);
162 | wait_50ms(); //
163 | deposit(200);
164 | wait_50ms(); //
165 | deposit(700);
166 | wait_50ms(); // To make sure the deposits are in the sequentially correct order
167 | if (balance == 1000) {
168 | System.out.println("Test passed!");
169 | } else {
170 | System.out.println("Test failed!");
171 | }
172 | }
173 |
174 | public static void test_case1() {
175 | balance = 0;
176 | deposit(100);
177 | deposit(200);
178 | deposit(700);
179 | }
180 |
181 | public static void test_case2() {
182 | balance = 0;
183 | deposit(1000);
184 | withdraw(1000);
185 | withdraw(1000);
186 | withdraw(1000);
187 | withdraw(1000);
188 | withdraw(1000);
189 | }
190 |
191 | public static void test_case3() {
192 | balance = 0;
193 | withdraw(1000);
194 | deposit(500);
195 | deposit(500);
196 | withdraw(500);
197 | withdraw(500);
198 | withdraw(1000);
199 | }
200 |
201 | public static void test_case4() {
202 | balance = 0;
203 | deposit(2000);
204 | withdraw(500);
205 | withdraw(1000);
206 | withdraw(1500);
207 | deposit(4000);
208 | withdraw(2000);
209 | withdraw(2500);
210 | withdraw(3000);
211 | deposit(5000);
212 | withdraw(3500);
213 | withdraw(4000);
214 | }
215 |
216 | public static void main(String[] args) {
217 | // Uncomment Tests Cases as you go
218 | // test_case0();
219 | // test_case1();
220 | // test_case2();
221 | // test_case3();
222 | test_case4();
223 | }
224 |
225 | }
226 |
--------------------------------------------------------------------------------
/Multi-threading in Java/Bank User Example from 15-619 Cloud Computing/F17 15-619 Cloud Computing- (writeup: Thread-safe programming in Java) - TheProject.Zone.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ziang-Lu/Multiprocessing-and-Multithreading/8d61a4479517fe8f1711f069c388d5828753092b/Multi-threading in Java/Bank User Example from 15-619 Cloud Computing/F17 15-619 Cloud Computing- (writeup: Thread-safe programming in Java) - TheProject.Zone.pdf
--------------------------------------------------------------------------------
/Multi-threading in Java/Bank User Example from 15-619 Cloud Computing/S17 15-619 Cloud Computing- (writeup: Introduction to multithreaded programming in Java) - TheProject.Zone.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ziang-Lu/Multiprocessing-and-Multithreading/8d61a4479517fe8f1711f069c388d5828753092b/Multi-threading in Java/Bank User Example from 15-619 Cloud Computing/S17 15-619 Cloud Computing- (writeup: Introduction to multithreaded programming in Java) - TheProject.Zone.pdf
--------------------------------------------------------------------------------
/Multi-threading in Java/BlockingQueueDemo.java:
--------------------------------------------------------------------------------
1 | import java.util.Random;
2 | import java.util.concurrent.ArrayBlockingQueue;
3 | import java.util.concurrent.BlockingQueue;
4 |
5 | /**
6 | * Producer class.
7 | * Writes data to an atomic message queue
8 | */
9 | class Producer implements Runnable {
10 | /**
11 | * Random number generator to use.
12 | */
13 | private final Random random;
14 | /**
15 | * Atomic message queue used by this producer.
16 | */
17 | private final BlockingQueue queue;
18 |
19 | /**
20 | * Constructor with parameter.
21 | * @param queue atomic message queue to use
22 | */
23 | Producer(BlockingQueue queue) {
24 | random = new Random();
25 | this.queue = queue;
26 | }
27 |
28 | @Override
29 | public void run() {
30 | try {
31 | // Produce messages
32 | for (int i = 0; i < 5; ++i) {
33 | // Simulate the producing process by sleeping for 1~3 seconds
34 | Thread.sleep((random.nextInt(2) + 1) * 1000);
35 | String msg = String.format("Message-%d", i);
36 | queue.put(msg);
37 | System.out.println("Producer put " + msg + " into the queue");
38 | }
39 | // Put a special value indicating that the message queue should shut down
40 | queue.put("exit");
41 | } catch (InterruptedException e) {
42 | e.printStackTrace();
43 | }
44 | }
45 | }
46 |
47 | /**
48 | * Consumer class.
49 | * Reads data from an atomic message queue
50 | */
51 | class Consumer implements Runnable {
52 | /**
53 | * Random number generator to use.
54 | */
55 | private final Random random;
56 | /**
57 | * Atomic queue used by this consumer.
58 | */
59 | private final BlockingQueue queue;
60 |
61 | /**
62 | * Constructor with parameter.
63 | * @param queue atomic message queue to use
64 | */
65 | Consumer(BlockingQueue queue) {
66 | random = new Random();
67 | this.queue = queue;
68 | }
69 |
70 | @Override
71 | public void run() {
72 | try {
73 | String msg = queue.take();
74 | // Check for the special value indicating that the message queue should shut down
75 | while (!msg.equals("exit")) {
76 | System.out.println("Consumer got " + msg + " from the queue");
77 | // Simulate the consuming process by sleeping for 1~5 seconds
78 | Thread.sleep((random.nextInt(4) + 1) * 1000);
79 | msg = queue.take();
80 | }
81 | } catch (InterruptedException e) {
82 | e.printStackTrace();
83 | }
84 | }
85 | }
86 |
87 | /**
88 | * Simple demo for using BlockingQueue as an atomic message queue.
89 | *
90 | * @author Ziang Lu
91 | */
92 | public class BlockingQueueDemo {
93 |
94 | /**
95 | * Main driver.
96 | * @param args arguments from command line
97 | */
98 | public static void main(String[] args) {
99 | BlockingQueue queue = new ArrayBlockingQueue<>(3);
100 | new Thread(new Producer(queue)).start();
101 | new Thread(new Consumer(queue)).start();
102 |
103 | /*
104 | * Output:
105 | * Producer put Message-0 into the queue
106 | * Consumer got Message-0 from the queue
107 | * Producer put Message-1 into the queue
108 | * Consumer got Message-1 from the queue
109 | * Producer put Message-2 into the queue
110 | * Consumer got Message-2 from the queue
111 | * Producer put Message-3 into the queue
112 | * Producer put Message-4 into the queue
113 | * Consumer got Message-3 from the queue
114 | * Consumer got Message-4 from the queue
115 | */
116 | }
117 |
118 | }
119 |
--------------------------------------------------------------------------------
/Multi-threading in Java/Locks and Sync Blocking/WaitBlockingDemo.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Self-defined queue to synchronize on.
3 | */
4 | class MyQueue {
5 | /**
6 | * Underlying number in the queue.
7 | */
8 | private int n;
9 | /**
10 | * Whether the number is set by put() method.
11 | */
12 | boolean numIsSet = false;
13 |
14 | /**
15 | * Accessor of n.
16 | * @return n
17 | */
18 | int get() {
19 | System.out.println("Got " + n + " from queue");
20 | return n;
21 | }
22 |
23 | /**
24 | * Mutator of n
25 | * @param n n
26 | */
27 | void put(int n) {
28 | System.out.println("Put " + n + " into the queue");
29 | this.n = n;
30 | }
31 | }
32 |
33 | /**
34 | * Self-defined Producer class that implements Runnable interface.
35 | * Writes data to a synchronized queue
36 | */
37 | class Producer implements Runnable {
38 | /**
39 | * Queue to write data to.
40 | */
41 | private final MyQueue queue;
42 |
43 | /**
44 | * Constructor with parameter.
45 | * @param queue queue to write data to
46 | */
47 | Producer(MyQueue queue) {
48 | this.queue = queue;
49 | }
50 |
51 | @Override
52 | public void run() {
53 | int i = 0;
54 | while (i <= 5) {
55 | synchronized (queue) {
56 | while (queue.numIsSet) {
57 | try {
58 | System.out.println("Producer waiting...");
59 | queue.wait();
60 | System.out.println("Producer got queue's monitor");
61 | } catch (InterruptedException e) {
62 | System.out.println(Thread.currentThread().getName() + " interrupted");
63 | }
64 | }
65 | queue.put(i);
66 | queue.numIsSet = true;
67 | queue.notify();
68 | ++i;
69 | }
70 | }
71 | }
72 | }
73 |
74 | /**
75 | * Self-defined Consumer class that implements Runnable interface.
76 | * Reads data from a synchronized queue
77 | */
78 | class Consumer implements Runnable {
79 | /**
80 | * Queue to read data from.
81 | */
82 | private final MyQueue queue;
83 |
84 | /**
85 | * Constructor with parameter.
86 | * @param queue queue to read data from
87 | */
88 | Consumer(MyQueue queue) {
89 | this.queue = queue;
90 | }
91 |
92 | @Override
93 | public void run() {
94 | while (true) {
95 | synchronized (queue) {
96 | while (!queue.numIsSet) {
97 | try {
98 | System.out.println("Consumer waiting...");
99 | queue.wait();
100 | System.out.println("Consumer got queue's monitor");
101 | } catch (InterruptedException e) {
102 | System.out.println(Thread.currentThread().getName() + " interrupted");
103 | }
104 | }
105 | queue.get();
106 | queue.numIsSet = false;
107 | queue.notify();
108 | }
109 | }
110 | }
111 | }
112 |
113 | public class WaitBlockingDemo {
114 |
115 | /**
116 | * Main driver.
117 | * @param args arguments from command line
118 | */
119 | public static void main(String[] args) {
120 | MyQueue queue = new MyQueue();
121 | Thread producer = new Thread(new Producer(queue), "Thread-Producer");
122 | Thread consumer = new Thread(new Consumer(queue), "Thread-Consumer");
123 | producer.start();
124 | consumer.start();
125 | try {
126 | producer.join();
127 | consumer.join();
128 | } catch (InterruptedException e) {
129 | System.out.println(Thread.currentThread().getName() + " interrupted");
130 | }
131 |
132 | /*
133 | * Output:
134 | * Put: 1 into the queue
135 | * Producer waiting...
136 | * Got: 1 from queue
137 | * Consumer waiting...
138 | * Producer got queue's monitor
139 | * Put: 2 into the queue
140 | * Producer waiting...
141 | * Consumer got queue's monitor
142 | * Got: 2 from queue
143 | * Consumer waiting...
144 | * Producer got queue's monitor
145 | * Put: 3 into the queue
146 | * Producer waiting...
147 | * Consumer got queue's monitor
148 | * Got: 3 from queue
149 | * Consumer waiting...
150 | * Producer got queue's monitor
151 | * Put: 4 into the queue
152 | * Producer waiting...
153 | * Consumer got queue's monitor
154 | * Got: 4 from the queue
155 | * Consumer waiting...
156 | * Producer got queue's monitor
157 | * Put: 5 into the queue
158 | * Consumer got queue's monitor
159 | * Got: 5 from the queue
160 | * Consumer waiting...
161 | */
162 | }
163 |
164 | }
165 |
--------------------------------------------------------------------------------
/Multi-threading in Java/Multi-threading in Java.md:
--------------------------------------------------------------------------------
1 | # Mult-threading in Java
2 |
3 | ## Basic Implementations
4 |
5 | * Implementing `Runnable` interface, and therefore implement `run()` method
6 |
7 | ```java
8 | /**
9 | * Self-defined class that implements Runnable interface.
10 | */
11 | class DisplayMessage implements Runnable {
12 | /**
13 | * Message to display.
14 | */
15 | private final String msg;
16 |
17 | /**
18 | * Constructor with parameter.
19 | * @param msg message to display
20 | */
21 | DisplayMessage(String msg) {
22 | this.msg = msg;
23 | }
24 |
25 | @Override
26 | public void run() {
27 | try {
28 | for (int i = 0; i < 3; ++i) {
29 | System.out.println(msg);
30 | Thread.sleep((long) (500 + Math.random() * 500));
31 | }
32 | } catch (InterruptedException ex) {
33 | System.out.println(Thread.currentThread().getName() + " interrupted");
34 | }
35 | }
36 | }
37 |
38 | /**
39 | * Simple demo for multi-threading with implementing Runnable interface.
40 | *
41 | * @author Ziang Lu
42 | */
43 | public class SimpleDemo {
44 |
45 | /**
46 | * Main driver.
47 | * @param args arguments from command line
48 | */
49 | public static void main(String[] args) {
50 | Thread helloThread = new Thread(new DisplayMessage("hello"), "Thread-Hello");
51 | System.out.println("Starting hello thread...");
52 | helloThread.start();
53 |
54 | Thread byeThread = new Thread(new DisplayMessage("bye"), "Thread-Bye");
55 | System.out.println("Starting bye thread...");
56 | byeThread.start();
57 |
58 | try {
59 | helloThread.join();
60 | byeThread.join();
61 | } catch (InterruptedException ex) {
62 | System.out.println(Thread.currentThread().getName() + " interrupted");
63 | }
64 | System.out.println();
65 |
66 | /*
67 | * Output:
68 | * Starting hello thread...
69 | * Starting bye thread...
70 | * hello
71 | * bye
72 | * hello
73 | * bye
74 | * hello
75 | * bye
76 | */
77 | }
78 |
79 | }
80 | ```
81 |
82 | * Extending `Thread` class, and override `run()` method
83 |
84 | ```java
85 | /**
86 | * Self-defined class that extends Thread class.
87 | */
88 | class GuessANumber extends Thread {
89 | /**
90 | * Number to guess.
91 | */
92 | private final int num;
93 |
94 | /**
95 | * Constructor with parameter.
96 | * @param num number to guess
97 | */
98 | GuessANumber(int num) {
99 | this.num = num;
100 | }
101 |
102 | @Override
103 | public void run() {
104 | String threadName = Thread.currentThread().getName();
105 | int counter = 0;
106 | int guess = 0;
107 | do {
108 | guess = (int) (Math.random() * 10 + 1);
109 | System.out.println(threadName + " guesses " + guess);
110 | ++counter;
111 | } while (guess != num);
112 | System.out.println("Correct! " + threadName + " uses " + counter + " guesses.");
113 | }
114 | }
115 |
116 | /**
117 | * Simple demo for multi-threading with extending Thread class.
118 | *
119 | * @author Ziang Lu
120 | */
121 | public class SimpleDemo {
122 |
123 | /**
124 | * Main driver.
125 | * @param args arguments from command line
126 | */
127 | public static void main(String[] args) {
128 | Thread guessThread = new GuessANumber(7);
129 | guessThread.setName("Thread-Guess");
130 | System.out.println("Starting guess thread...");
131 | guessThread.start();
132 | try {
133 | guessThread.join();
134 | } catch (InterruptedException ex) {
135 | System.out.println(Thread.currentThread().getName() + " interrupted");
136 | }
137 |
138 | /*
139 | * Output:
140 | * Starting guess thread...
141 | * Thread-Guess guesses 9
142 | * Thread-Guess guesses 8
143 | * Thread-Guess guesses 1
144 | * Thread-Guess guesses 7
145 | * Correct! Thread-Guess uses 4 guesses.
146 | */
147 | }
148 |
149 | }
150 | ```
151 |
152 | * Use `ExecutorService` class to create a pool of threads
153 |
154 | ```java
155 | import java.util.Random;
156 | import java.util.concurrent.ExecutorService;
157 | import java.util.concurrent.Executors;
158 | import java.util.concurrent.TimeUnit;
159 |
160 | /**
161 | * Self-defined class that implements Runnable interface.
162 | */
163 | class Task implements Runnable {
164 | /**
165 | * Task ID.
166 | */
167 | private int id;
168 | /**
169 | * Random number generator to use.
170 | */
171 | private Random random;
172 |
173 | /**
174 | * Constructor with parameter.
175 | * @param id task ID
176 | */
177 | Task(int id) {
178 | this.id = id;
179 | random = new Random();
180 | }
181 |
182 | @Override
183 | public void run() {
184 | System.out.println("Starting task-" + id);
185 | // To simulate the task execution, sleep a random period between 1~5 seconds
186 | try {
187 | Thread.sleep((random.nextInt(4) + 1) * 1000);
188 | } catch (InterruptedException e) {
189 | e.printStackTrace();
190 | }
191 | System.out.println("Completed task-" + id);
192 | }
193 | }
194 |
195 | /**
196 | * Simple demo for creating and using a thread pool.
197 | *
198 | * @author Ziang Lu
199 | */
200 | public class ThreadPoolDemo {
201 |
202 | /**
203 | * Main driver.
204 | * @param args arguments from command line
205 | */
206 | public static void main(String[] args) {
207 | // Create a thread pool with 3 threads
208 | ExecutorService pool = Executors.newFixedThreadPool(3);
209 | // Submit tasks for execution
210 | for (int i = 0; i < 5; ++i) {
211 | pool.submit(new Task(i));
212 | }
213 | // We need to call shutdown() to indicate to the executor service (thread pool) that no more tasks are allowed
214 | // to be submitted.
215 | // If we don't call shutdown(), the program will never end, since the executor service (thread pool) keeps
216 | // waiting for more tasks to be submitted.
217 | pool.shutdown();
218 | // After calling shutdown(), no more tasks are allowed to be submitted; when all the submitted tasks finished
219 | // execution, this executor service (thread pool) is terminated.
220 | System.out.println("All tasks submitted and the executor service (thread pool) is shut down.");
221 |
222 | // Check: After calling shutdown(), submitting a new task throws a RejectedExecutionException
223 | // pool.submit(new Task(5));
224 |
225 | // Wait up to 60 seconds for all the submitted tasks to finish execution
226 | System.out.println("Waiting for all tasks to finish execution...");
227 | try {
228 | pool.awaitTermination(60, TimeUnit.SECONDS); // Block until all the submitted tasks finish execution
229 | } catch (InterruptedException e) {
230 | e.printStackTrace();
231 | pool.shutdownNow();
232 | // This will force the executor service (thread pool) to shut down and terminate, by attempting to stop the
233 | // executing tasks, and prevent waiting tasks from starting.
234 | }
235 | // Upon termination, the executor service has no tasks actively executing, no tasks currently awaiting
236 | // execution, and no new tasks are allowed to be submitted.
237 | System.out.println("All tasks completed.");
238 |
239 | /*
240 | * Output:
241 | * All tasks submitted and the executor service (thread pool) is shut down.
242 | * Waiting for all tasks to finish execution...
243 | * Starting task-2
244 | * Starting task-1
245 | * Starting task-0
246 | * Completed task-2
247 | * Completed task-0
248 | * Starting task-4
249 | * Starting task-3
250 | * Completed task-1
251 | * Completed task-3
252 | * Completed task-4
253 | * All tasks completed.
254 | */
255 | }
256 |
257 | }
258 | ```
259 |
260 |
261 |
262 | ## Java Thread的wait和notify
263 |
264 | **In Java, every object has a unique internal lock.** When a method is declared as ``synchronized``, or a code snippet is enclosed by ``synchronized (this)`` block, that method or code snippet will be protected by an internal lock. When any thread wants to enter the method or the code snippet, it must first try to acquire the internal lock.
265 |
266 | This ensures that only one method can be executed on the object at any given point. Other methods can invoke the method or enter the code snippet, however they have to wait until the running thread releases the lock by exiting from the protected area or call wait() on the lock.
267 |
268 | **相当于每个Object自带一个锁 (被称为monitor), 即每个Object自带一个锁定池和等待池**
269 |
270 |
271 |
272 | A thread becomes the owner of the object's monitor in one of the three ways:
273 |
274 | - By executing a ``synchronized`` instance method of that object
275 | - By executing the body of a ``synchronized`` statement that synchronizes on the object
276 | - For objects of type Class, by executing a ``synchronized `` static method of that class
277 |
278 |
279 |
280 | 当前thread拥有某个object的monitor时, 可以选择obj.wait()来使自己进入等待阻塞, 同时释放obj的monitor
281 |
282 | 当obj的monitor之后的拥有者thread call了obj.notify(), 唤醒obj的等待池中的任意一个thread, 收到通知的thread将自动尝试获得obj的monitor (进入obj的锁定池), 但当前thread不会释放obj的monitor
283 |
284 | 若obj的monitor之后的拥有者thread call了obj.notifyAll(), 唤醒obj的等待池中的全部thread, 收到通知的全部thread将自动尝试获得obj的monitor (进入obj的锁定池), 但当前thread不会释放obj的monitor
285 |
286 | *注意: 只有当某个thread重新获得了obj的monitor之后才会从obj.wait()中退出, 即obj.wait()涵盖了wait()和在obj的锁定池中成功获得锁定两部分*
287 |
288 |
289 |
290 | 注意:
291 |
292 | A thread can also wake up without being notified, interrupted, or timing out, a so-called *spurious wakeup*. While this will rarely occur in practice, applications must guard against it by testing for the condition that should have caused the thread to be awakened, and continuing to wait if the condition is not satisfied. In other words, waits should always occur in loops, like this one:
293 |
294 | ```java
295 | synchronized (obj) { // 执行synchronized block on obj, 获得obj的monitor
296 | while () {
297 | obj.wait();
298 | }
299 | // Perform action appropriate to condition
300 | }
301 | ```
302 |
303 |
304 |
305 | 注意:
306 |
307 | A more common mistake is synchronizing on threads' own lock (``synchronized (this)``). Since each instance has its own internal block, by using thread's own lock, the code block is not protected by a global lock and every thread can access it at any time. Thus, this kind of synchronization will fail to work.
308 |
309 | ```java
310 | // Bad example
311 | public void uselessSync() {
312 | new Thread(new Runnable() {
313 | @Override
314 | public void run() {
315 | synchronized (this) { // Use the current thread's monitor as the lock, but not a global shared lock
316 | // Do something
317 | }
318 | }
319 | })
320 | }
321 | ```
322 |
323 | 本质上: **Always synchronize on the same lock before accessing the shared object**
324 |
325 | ```java
326 | // Good example
327 | private Object lock = new Object();
328 |
329 | public void sync() {
330 | new Thread(new Runnable() {
331 | @Override
332 | public void run() {
333 | synchronized (lock) { // Use a global shared lock
334 | // Do something
335 | }
336 | }
337 | })
338 | }
339 | ```
340 |
341 |
342 |
343 | ## Thread-safety in Java Libraries
344 |
345 | There are five levels thread-safety in Java library:
346 |
347 | 1. **Immutable**
348 |
349 | Immutable objects cannot be changed or modified, so we can safely share it among threads and do not need to do any synchronization.
350 |
351 | 2. **Unconditionally thread-safe**
352 |
353 | These objects are mutable, but have implemented sufficient "internal synchronization", which means that locking has already been designed and implemented by Java standard library developers, and we do not need to synchronize by ourselves manually. Thus, we can safely use them.
354 |
355 | e.g., `AtomicInteger`, `Random`, ` ConcurrentHashMap`, `ConcurrentHashSet`, `BlockingQueue`, `PriorityBlockingQueue`...
356 |
357 | ```java
358 | ConcurrentHashSet set = new ConcurrentHashSet();
359 |
360 | public void add(String s) {
361 | set.add(s);
362 | }
363 | ```
364 |
365 | 3. **Conditionally thread-safe**
366 |
367 | These objects are mutable, but have implemented sufficient "internal synchronization" on most of the methods. However, some methods still need to explicitly perform external synchronization.
368 |
369 | 4. **Thread-unsafe**
370 |
371 | These objects are mutable and implemented with no internal synchronization. In order to use them safely, please explicitly synchronize ANY method call to these objects.
372 |
373 | e.g., `StringBuilder`, most of the Java `Collections`, such as `ArrayList`, `LinkedList`, `HashMap`, `HashSet`...
374 |
375 | ```java
376 | HashSet set = new HashSet();
377 |
378 | public synchronized void add(String s) {
379 | set.add(s);
380 | }
381 | ```
382 |
383 | 5. **Thread-hostile**
384 |
385 | These objects are not thread-safe even if you have already used external synchronization on ANY method call.
386 |
387 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This repo contains multi-threading related stuff in Java and Python, and multi-processing related stuff in Python.
2 |
3 | # Multi-threading
4 |
5 | ## Thread Safety
6 |
7 | Thread-safe code must ensure that when multiple threads are accessing a shared object, no matter how these threads are scheduled or in what order they are executing, this object will always behave correctly without any external synchronization, and every thread will be able to see any changes happened on this object immediately.
8 |
9 |
10 |
11 | ## Race Condition
12 |
13 | A program attempts to do some parallel operations at the same time, but requires that the operations are done in a specific order that is not guaranteed.
14 |
15 | *(They are difficult to detect because oftentimes, the ordering may be correct, making the system appear functional.)*
16 |
17 | **Whenever there is read from/write to shared global variables, there could be race condition.**
18 |
19 | => Check out `Multi-processsing and Multi-threading in Python Examples/Multi-threading/race_condition_demo/race_condition.py`
20 |
21 | ### Away with Race Conditions: (-> Thread-Safe)
22 |
23 | 1. Ensure an explicit ordering of the operations (on the shared resources)
24 |
25 | All operations (on the shared resources) must be executed in the same order they are received.
26 |
27 | *=> To ensure this, we simply put the operations (on one shared resource) in a single thread.*
28 |
29 | 2. Restrict access to the shared resource
30 |
31 | Only one operation can access the shared resource at the same time. During the period of access, no other operations can read or change its value.
32 |
33 | #### 1. Solution with Immutable Objects
34 |
35 | Check out this design pattern: Immutable Object Pattern for more details
36 |
37 | Since a class must have a lot of restrictions to be immutable, this solution is not very feasible in many practical cases.
38 |
39 | #### 2. Solution with Atomic Operations & Classes ***
40 |
41 | **Atomic operations are performed in a single unit of task, without interference from other operations.**
42 |
43 | e.g., Java `AtomicInteger`, `Random`, `ConcurrentHashMap`, `ConcurrentHashSet`, and the following (atomic) blocking queue interface and implementations
44 |
45 | ***
46 |
47 | Solution with **(Atomic) Message Queue** ***
48 |
49 | 1. (对应于上面的1) Each shared resource shall be accessed in exactly its own thread.
50 | 2. (对应于上面的2) All communications with that thread shall be done using an atomic message queue
51 | * Java: `BlockingQueue` interface, with `ArrayBlockingQueue`, `LinkedBlockingQueue` and `PriorityBlockingQueue` impelementations
52 | * Python: `queue.Queue` and `queue.PriorityQueue` classes
53 |
54 | ***
55 |
56 | #### 3. Solution with Locks ***
57 |
58 | Note that many solutions above, like Atomic Operations & Classes and (Atomic) Message Queue, have built-in locks in their implementations, so using locks explicitly is considered a low-level synchronization operation and should be avoided
59 |
60 |
61 |
62 | ## Thread的wait和notify
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | ## Thread状态转换总结
73 |
74 |
75 |
76 |
77 |
78 | # Distributed Locking Mechanism (Using Redis)
79 |
80 | For a distributed scenario, we can use the Redis's single-threaded feature to
81 |
82 | - Implement a **naive** distributed lock
83 |
84 | Simple but imperfect
85 |
86 | => In NOT so high-concurrent scenarios, this works just fine.
87 |
88 | - Use a **third-party library**
89 |
90 | Complex but perfect
91 |
92 | => Works for high-concurrent scenarios
93 |
94 | Check out `distributed_locking.py`
95 |
96 |
97 |
98 | ## Comparison between Multi-threading, Multi-processing, and Asynchronous IO in Python
99 |
100 | After studying asynchronous IO in Python, we are able to compare these three concurrent strategies:
101 |
102 | | | Multi-processing | Multi-threading | Asynchronous IO |
103 | | --------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
104 | | Use blocking standard library functions | **YES** | **YES** | NO |
105 | | Optimize waiting periods | YES
Preemptive, because the OS handles subprocess scheduling | YES
Preemptive, because the OS handles thread scheduling | YES
Cooperative, because the Python interpreter itself handles coroutine scheduling |
106 | | Use all CPU cores | **YES**
=> 可以把thread/coroutine包在subprocess之中, 达到充分利用多核CPU的目的 | NO | NO |
107 | | GIL interference | **NO** | Some
(对于IO密集型程序, 由于CPU可以在thread等待期间执行其他thread, NO)
(对于CPU计算密集型程序, YES) | **NO** |
108 | | Scalability
(本质上, 与开销有关) | Low | Medium | **High**
=> 对于真正需要巨大scalability时, 才考虑使用async, 比如server需要处理大量requests时 |
109 |
110 |
111 |
112 | # License
113 |
114 | This repo is distributed under the MIT license.
115 |
--------------------------------------------------------------------------------
/distributed_locking.py:
--------------------------------------------------------------------------------
1 | #!usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Distributed locking mechanism using Redis.
6 |
7 | We can use Redis's single-threaded feature to
8 | 1. Implement a naive distributed lock
9 | Simple but imperfect
10 | => In NOT so high-concurrent scenarios, this works just fine.
11 | 2. Use a third-party library
12 | Complex but perfect
13 | => Works for high-concurrent scenarios
14 | """
15 |
16 | import redis
17 | from redlock import MultipleRedlockException, Redlock
18 |
19 | LOCK_KEY = 'lock'
20 |
21 |
22 | def set_up() -> None:
23 | """
24 | Stock setup.
25 | :return: None
26 | """
27 | r = redis.Redis()
28 |
29 | r.set('stock', 10)
30 |
31 |
32 | def lightning_order() -> None:
33 | """
34 | Lightning order.
35 | :return: None
36 | """
37 | r = redis.Redis()
38 |
39 | # Use a "lock" key as the lock
40 | # => We need to set the value of the "lock" key to be unique for every
41 | # client, so that when releasing the lock, we know whether this lock is
42 | # still owned by this client, rather than automatically released due to
43 | # timeout.
44 | client_id = r.client_id()
45 | result = r.setnx(LOCK_KEY, client_id)
46 | while not result: # If not acquiring the lock, block here
47 | result = r.setnx(LOCK_KEY, client_id)
48 |
49 | # Acquired the lock
50 | # => We need to set an expire time for "lock", so that eventually this lock
51 | # will be released.
52 | r.expire(LOCK_KEY, 30)
53 | # But how do we set the expire time?
54 | # => Estimate the execution time of the business codes, and set the expire
55 | # time to be longer than it, to make sure the client who acquired the
56 | # lock has enough time to execute the business codes.
57 |
58 | try:
59 | # Business codes
60 | remaining = int(r.get('stock'))
61 | if remaining > 0:
62 | r.set('stock', str(remaining - 1))
63 | print(f'Deducted stock, {remaining - 1} remaining')
64 | else:
65 | print('Failed to deduct stock')
66 | # PROBLEM:
67 | # If our web application goes down during the business codes, the
68 | # "finally" part still won't get executed, meaning that the client who
69 | # acquired the lock may NOT be able to release it, leading to a deadlock
70 | # forever.
71 | # => We need to set an expire time for "lock", so that eventually this
72 | # lock will be released.
73 | finally:
74 | # In case that the business codes may raise an exception, we should
75 | # release the lock in a "finally", by deleting "lock" key.
76 | # r.delete('lock')
77 |
78 | # PROBLEM:
79 | # What if the execution time of the business codes exceeds the expire
80 | # time for "lock"?
81 | # In this case, the lock is "released" before the execution of the
82 | # business codes, and some other client is able to acquire the same
83 | # lock, which is unsafe.
84 | # => We need to set the value of the "lock" key to be unique for every
85 | # client, so that when releasing the lock, we know whether this lock is
86 | # still owned by this client, rather than automatically released due to
87 | # timeout.
88 | lock_val = r.get(LOCK_KEY)
89 | if lock_val != client_id:
90 | raise Exception('Business codes timed out.')
91 | r.delete(LOCK_KEY)
92 |
93 |
94 | def lightning_order_with_redlock() -> None:
95 | """
96 | Lightning order with Redlock algorithm.
97 | :return: None
98 | """
99 | r = redis.Redis()
100 |
101 | dlm = Redlock([{
102 | 'host': 'localhost',
103 | 'port': 6379,
104 | 'db': 0
105 | }, ]) # Stands for "distributed lock manager"
106 |
107 | lock = None
108 | try:
109 | # Try to acquire the lock
110 | lock = dlm.lock(LOCK_KEY, 30000) # If not acquiring the lock, block here
111 | # Business codes
112 | remaining = int(r.get('stock'))
113 | if remaining > 0:
114 | r.set('stock', str(remaining - 1))
115 | print(f'Deducted stock, {remaining - 1} remaining')
116 | else:
117 | print('Failed to deduct stock')
118 | except MultipleRedlockException as e:
119 | print(e)
120 | finally:
121 | # Release the lock
122 | dlm.unlock(lock)
123 |
--------------------------------------------------------------------------------
/thread_notify.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ziang-Lu/Multiprocessing-and-Multithreading/8d61a4479517fe8f1711f069c388d5828753092b/thread_notify.png
--------------------------------------------------------------------------------
/thread_status.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ziang-Lu/Multiprocessing-and-Multithreading/8d61a4479517fe8f1711f069c388d5828753092b/thread_status.png
--------------------------------------------------------------------------------
/thread_wait.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ziang-Lu/Multiprocessing-and-Multithreading/8d61a4479517fe8f1711f069c388d5828753092b/thread_wait.png
--------------------------------------------------------------------------------