├── README.md └── threads └── src ├── 01-single_thread.py ├── 02-multi_threaded.py ├── 03-daemon_threads.py ├── 03-daemon_threads_timeout.py ├── 04-join_threads.py ├── 04-join_threads_timeout.py ├── 05-thread_pool.py ├── 06-race_condition.py ├── 06-race_condition_with_lock.py ├── 07-deadlock.py ├── 07-lock.py ├── 08-race_condition_with_lock.py ├── 09-rlock.py ├── 10-timer.py ├── Dockerfile ├── Makefile ├── README.md ├── requirements.txt └── training.py /README.md: -------------------------------------------------------------------------------- 1 | # O'Reilly Live Trainings 2 | -------------------------------------------------------------------------------- /threads/src/01-single_thread.py: -------------------------------------------------------------------------------- 1 | # Example of a single-threaded application 2 | from training import WEBSITES, visit_website 3 | 4 | 5 | if __name__ == '__main__': 6 | print('Main thread starting') 7 | for website in WEBSITES: 8 | visit_website(website) 9 | print('Main thread ending') -------------------------------------------------------------------------------- /threads/src/02-multi_threaded.py: -------------------------------------------------------------------------------- 1 | # Example of a multi-threaded application 2 | from training import WEBSITES, visit_website 3 | import threading 4 | 5 | 6 | if __name__ == '__main__': 7 | print('Main thread starting') 8 | for website in WEBSITES: 9 | # Create a Thread object with target and args 10 | t = threading.Thread(target=visit_website, args=[website]) 11 | # Start the thread 12 | t.start() 13 | print('Main thread ending') -------------------------------------------------------------------------------- /threads/src/03-daemon_threads.py: -------------------------------------------------------------------------------- 1 | # Example of a multi-threaded application using daemon threads 2 | from training import WEBSITES, visit_website 3 | import threading 4 | 5 | 6 | if __name__ == '__main__': 7 | print('Main thread starting') 8 | for website in WEBSITES: 9 | # Create a Thread object with target and args 10 | t = threading.Thread(target=visit_website, args=[website], daemon=True) 11 | # Start the thread 12 | t.start() 13 | print('Main thread ending') -------------------------------------------------------------------------------- /threads/src/03-daemon_threads_timeout.py: -------------------------------------------------------------------------------- 1 | # Example of using daemon threads to terminate threads when the main thread terminates 2 | from training import WEBSITES, visit_website 3 | import threading 4 | import sys 5 | import time 6 | 7 | 8 | if __name__ == '__main__': 9 | # Create a forced timeout option 10 | if len(sys.argv) != 2: 11 | print(f'Usage: {sys.argv[0]} TIMEOUT') 12 | sys.exit() 13 | 14 | print('Main thread starting') 15 | 16 | # The program will end after `timeout` seconds 17 | timeout = int(sys.argv[1]) 18 | 19 | for website in WEBSITES: 20 | # Create and start some daemon threads 21 | t = threading.Thread(target=visit_website, args=[website], daemon=True) 22 | t.start() 23 | 24 | # Force the program to end after timeout 25 | time.sleep(timeout) 26 | 27 | print('Main thread ending') -------------------------------------------------------------------------------- /threads/src/04-join_threads.py: -------------------------------------------------------------------------------- 1 | # Example of a multi-threaded application using start() and join() 2 | from training import WEBSITES, visit_website 3 | import threading 4 | 5 | 6 | if __name__ == '__main__': 7 | print('Main thread starting') 8 | for website in WEBSITES: 9 | # Create, start, and join a thread 10 | t = threading.Thread(target=visit_website, args=[website]) 11 | t.start() 12 | t.join() 13 | print('Main thread ending') -------------------------------------------------------------------------------- /threads/src/04-join_threads_timeout.py: -------------------------------------------------------------------------------- 1 | # Example of a multi-threaded application using start() and join() with a timeout 2 | from training import WEBSITES, visit_website 3 | import threading 4 | 5 | 6 | if __name__ == '__main__': 7 | print('Main thread starting') 8 | for website in WEBSITES: 9 | # Create, start and join a daemon thread that times out 10 | t = threading.Thread(target=visit_website, args=[website], daemon=True) 11 | t.start() 12 | t.join(timeout=1) 13 | print('Main thread ending') -------------------------------------------------------------------------------- /threads/src/05-thread_pool.py: -------------------------------------------------------------------------------- 1 | # Example of a multi-threaded application using ThreadPoolExecutor 2 | from training import WEBSITES, visit_website 3 | from concurrent.futures import ThreadPoolExecutor 4 | 5 | 6 | if __name__ == '__main__': 7 | print('Main thread starting') 8 | # Use the ThreadPoolExecutor context manager to manage threads 9 | with ThreadPoolExecutor(max_workers=3) as executor: 10 | for website in WEBSITES: 11 | # Submit a function and args to the pool of threads 12 | executor.submit(visit_website, website) 13 | print('Main thread ending') -------------------------------------------------------------------------------- /threads/src/06-race_condition.py: -------------------------------------------------------------------------------- 1 | # Example of a race condition 2 | from training import Account 3 | from concurrent.futures import ThreadPoolExecutor 4 | 5 | 6 | if __name__ == '__main__': 7 | print('Main thread starting') 8 | account = Account() 9 | print(account) 10 | with ThreadPoolExecutor(max_workers=2) as executor: 11 | # Submit two threads: deposit 100, followed by withdrawal 50 12 | for transaction, amount in [(account.deposit, 100), (account.withdrawal, 50)]: 13 | executor.submit(transaction, amount) 14 | print(account) 15 | print('Main thread ending') 16 | 17 | -------------------------------------------------------------------------------- /threads/src/06-race_condition_with_lock.py: -------------------------------------------------------------------------------- 1 | # Example of a race condition 2 | from training import ThreadSafeAccount 3 | from concurrent.futures import ThreadPoolExecutor 4 | 5 | 6 | if __name__ == '__main__': 7 | print('Main thread starting') 8 | account = ThreadSafeAccount() 9 | print(account) 10 | with ThreadPoolExecutor(max_workers=2) as executor: 11 | # Submit two threads: deposit 100, followed by withdrawal 50 12 | for transaction, amount in [(account.deposit, 100), (account.withdrawal, 50)]: 13 | executor.submit(transaction, amount) 14 | print(account) 15 | print('Main thread ending') 16 | 17 | -------------------------------------------------------------------------------- /threads/src/07-deadlock.py: -------------------------------------------------------------------------------- 1 | # Example of a deadlock situation 2 | import threading 3 | 4 | 5 | if __name__ == '__main__': 6 | 7 | # Create a lock 8 | lock = threading.Lock() 9 | print(lock) 10 | 11 | # Acquire a lock 12 | lock.acquire() 13 | print(lock) 14 | 15 | # Acquire a lock again -- deadlock! 16 | lock.acquire() 17 | print(lock) 18 | 19 | # Release the lock 20 | lock.release() 21 | print(lock) 22 | 23 | """ 24 | 25 | lock = threading.Lock() 26 | 27 | SHARED_DATA = 1 28 | 29 | def func(): 30 | 31 | lock.acquire() <-- another thread tries to access SHARED_DATA 32 | lock.acquire() 33 | SHARED_DATA += 1 34 | lock.release() 35 | 36 | """ 37 | -------------------------------------------------------------------------------- /threads/src/07-lock.py: -------------------------------------------------------------------------------- 1 | # Example of a locking using Lock objects 2 | import threading 3 | 4 | 5 | if __name__ == '__main__': 6 | 7 | # Create a lock 8 | lock = threading.Lock() 9 | print(lock) 10 | 11 | # Acquire a lock for a section of code 12 | lock.acquire() 13 | print(lock) 14 | 15 | # Release the lock 16 | lock.release() 17 | print(lock) 18 | 19 | """ 20 | 21 | lock = threading.Lock() 22 | 23 | SHARED_DATA = 1 24 | 25 | def func(): 26 | 27 | lock.acquire() 28 | SHARED_DATA += 1 29 | lock.release() 30 | 31 | """ 32 | -------------------------------------------------------------------------------- /threads/src/08-race_condition_with_lock.py: -------------------------------------------------------------------------------- 1 | # Example of a race condition 2 | from training import ThreadSafeAccount 3 | from concurrent.futures import ThreadPoolExecutor 4 | 5 | 6 | if __name__ == '__main__': 7 | print('Main thread starting') 8 | account = ThreadSafeAccount() 9 | print(account) 10 | with ThreadPoolExecutor(max_workers=2) as executor: 11 | # Submit two threads: deposit 100, followed by withdrawal 50 12 | for transaction, amount in [(account.deposit, 100), (account.withdrawal, 50)]: 13 | executor.submit(transaction, amount) 14 | print(account) 15 | print('Main thread ending') 16 | 17 | -------------------------------------------------------------------------------- /threads/src/09-rlock.py: -------------------------------------------------------------------------------- 1 | # Example of using RLocks 2 | # Source: https://stackoverflow.com/a/16568426/7811791 3 | import threading 4 | import time 5 | 6 | 7 | class Counter: 8 | def __init__(self): 9 | self.a = 1 10 | self.b = 2 11 | self.lock = threading.RLock() 12 | 13 | def changeA(self): 14 | with self.lock: 15 | self.a = self.a + 1 16 | 17 | def changeB(self): 18 | with self.lock: 19 | self.b = self.b + self.a 20 | 21 | def changeAandB(self): 22 | with self.lock: 23 | self.changeA() # a usual lock would block at here 24 | self.changeB() 25 | 26 | 27 | counter = Counter() 28 | 29 | counter.changeA() 30 | print(counter.a) 31 | 32 | counter.changeB() 33 | print(counter.b) 34 | 35 | counter.changeAandB() 36 | print(counter.a, counter.b) -------------------------------------------------------------------------------- /threads/src/10-timer.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import time 3 | 4 | def boom(): 5 | print('BOOM') 6 | 7 | if __name__ == '__main__': 8 | timer = threading.Timer(1, boom) 9 | timer.start() 10 | time.sleep(2) 11 | timer.cancel() -------------------------------------------------------------------------------- /threads/src/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3 2 | WORKDIR /src 3 | RUN apt-get update && apt-get install -y man 4 | ADD ./requirements.txt /src/requirements.txt 5 | RUN pip install -r requirements.txt 6 | ADD . /src -------------------------------------------------------------------------------- /threads/src/Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | docker build -t py3-threads . 3 | run: 4 | docker run -it --mount type=bind,source=$$(pwd),target=/src py3-threads bash 5 | clean: 6 | find . -name '__pycache__' | xargs rm -rf; -------------------------------------------------------------------------------- /threads/src/README.md: -------------------------------------------------------------------------------- 1 | # Build Fast and Efficient Python Applications Using Threads 2 | 3 | Source code for the O'Reilly live online training with Lee Gaines. 4 | 5 | We will use a Python3 Docker image to run the code. Assuming you have [installed Docker](https://docs.docker.com/get-docker/), navigate to the `src/threads` directory of this repo, then build the image with the following command: 6 | 7 | ``` 8 | docker build -t py3-threads . 9 | ``` 10 | 11 | To access the shell, run a container using this command: 12 | 13 | ``` 14 | docker run -it py3-threads bash 15 | ``` 16 | 17 | All of the source code will be located in the `/src` directory in the container. To sync this directory with your working directory when you run the container, use this command: 18 | 19 | ``` 20 | docker run -it --mount type=bind,source=$$(pwd),target=/src py3-threads bash 21 | ``` 22 | 23 | If you have `make` you can use the following commands to automate the build and run commands: 24 | 25 | ``` 26 | make build 27 | make run 28 | ``` 29 | 30 | ## Tips 31 | 32 | * To view the threads as a program is running: `ps -aLf` 33 | * Show information about the current thread: `threading.current_thread()` 34 | -------------------------------------------------------------------------------- /threads/src/requirements.txt: -------------------------------------------------------------------------------- 1 | requests -------------------------------------------------------------------------------- /threads/src/training.py: -------------------------------------------------------------------------------- 1 | # This modules defines variables and functions used as examples in this training. 2 | import requests 3 | import time 4 | import threading 5 | import ssl 6 | 7 | 8 | WEBSITES = [ 9 | 'http://mfa.go.th/main/', 10 | 'http://www.antarctica.gov.au/', 11 | 'http://www.mofa.gov.la/', 12 | 'http://www.presidency.gov.gh/', 13 | 'https://www.aph.gov.au/', 14 | 'https://www.argentina.gob.ar/', 15 | 'https://www.fmprc.gov.cn/mfa_eng/', 16 | 'https://www.gcis.gov.za/', 17 | 'https://www.gov.ro/en', 18 | 'https://www.government.se/', 19 | 'https://www.india.gov.in/', 20 | 'https://www.jpf.go.jp/e/', 21 | 'https://www.oreilly.com/', 22 | 'https://www.parliament.nz/en/', 23 | 'https://www.peru.gob.pe/', 24 | 'https://www.premier.gov.pl/en.html', 25 | 'https://www.presidence.gov.mg/', 26 | 'https://www.saskatchewan.ca/' 27 | ] 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | def visit_website(url): 39 | """Makes a request to a url and prints the status code and elapsed time""" 40 | try: 41 | response = requests.get(url) 42 | print(f'{url} returned {response.status_code} after {response.elapsed} seconds') 43 | except Exception as e: 44 | print(f'Failed to connect to {url}') 45 | pass 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | class Account(): 58 | def __init__(self): 59 | self.balance = 0 60 | 61 | def __repr__(self): 62 | return f'Current balance is {self.balance}' 63 | 64 | def deposit(self, amount): 65 | print(f'Depositing {amount}') 66 | # Simulates a database read and write 67 | state = self.balance 68 | time.sleep(0.1) 69 | state += amount 70 | self.balance = state 71 | 72 | def withdrawal(self, amount): 73 | print(f'Withdrawing {amount}') 74 | # Simulates a database read and write 75 | state = self.balance 76 | time.sleep(0.1) 77 | state -= amount 78 | self.balance = state 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | class ThreadSafeAccount(): 90 | def __init__(self): 91 | self.balance = 0 92 | self.lock = threading.Lock() # Give each account a Lock 93 | 94 | def __repr__(self): 95 | return f'Current balance is {self.balance}' 96 | 97 | def deposit(self, amount): 98 | print(f'Depositing {amount}') 99 | 100 | # Limit access to shared data to only one thread at a time 101 | with self.lock: 102 | state = self.balance 103 | state += amount 104 | time.sleep(0.1) 105 | self.balance = state 106 | 107 | def withdrawal(self, amount): 108 | print(f'Withdrawaling {amount}') 109 | 110 | # Limit access to shared data to only one thread at a time 111 | with self.lock: 112 | state = self.balance 113 | state -= amount 114 | time.sleep(0.1) 115 | self.balance = state 116 | --------------------------------------------------------------------------------