8000 Merge pull request #580 from realpython/thread-safety · realpython/materials@2c30025 · GitHub
[go: up one dir, main page]

8000
Skip to content

Commit 2c30025

Browse files
authored
Merge pull request #580 from realpython/thread-safety
Add code files from Thread Safety tutorial
2 parents d5abe40 + 72dd5c8 commit 2c30025

10 files changed

+353
-0
lines changed

thread-safety-locks/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Python Thread Safety: Using a Lock and Other Techniques
2+
3+
This folder contains code examples from the Real Python tutorial [Python Thread Safety: Using a Lock and Other Techniques](https://realpython.com/python-thread-lock/).
4+
5+
## About the Author
6+
7+
Adarsh Divakaran - Website: https://adarshd.dev/
8+
9+
## License
10+
11+
Distributed under the MIT license. See ``LICENSE`` for more information.

thread-safety-locks/bank_barrier.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import random
2+
import threading
3+
import time
4+
from concurrent.futures import ThreadPoolExecutor
5+
6+
teller_barrier = threading.Barrier(3)
7+
8+
9+
def prepare_for_work(name):
10+
print(f"{int(time.time())}: {name} is preparing their counter.")
11+
12+
# Simulate the delay to prepare the counter
13+
time.sleep(random.randint(1, 3))
14+
print(f"{int(time.time())}: {name} has finished preparing.")
15+
16+
# Wait for all tellers to finish preparing
17+
teller_barrier.wait()
18+
print(f"{int(time.time())}: {name} is now ready to serve customers.")
19+
20+
21+
tellers = ["Teller 1", "Teller 2", "Teller 3"]
22+
23+
with ThreadPoolExecutor(max_workers=3) as executor:
24+
for teller_name in tellers:
25+
executor.submit(prepare_for_work, teller_name)
26+
27+
print(f"{int(time.time())}: All tellers are ready to serve customers.")

thread-safety-locks/bank_condition.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import random
2+
import threading
3+
import time
4+
from concurrent.futures import ThreadPoolExecutor
5+
6+
customer_available_condition = threading.Condition()
7+
8+
# Customers waiting to be served by the Teller
9+
customer_queue = []
10+
11+
12+
def serve_customers():
13+
while True:
14+
with customer_available_condition:
15+
# Wait for a customer to arrive
16+
while not customer_queue:
17+
print(f"{int(time.time())}: Teller is waiting for a customer.")
18+
customer_available_condition.wait()
19+
20+
# Serve the customer
21+
customer = customer_queue.pop(0)
22+
print(f"{int(time.time())}: Teller is serving {customer}.")
23+
24+
# Simulate the time taken to serve the customer
25+
time.sleep(random.randint(1, 3))
26+
print(f"{int(time.time())}: Teller has finished serving {customer}.")
27+
28+
29+
def add_customer_to_queue(name):
30+
with customer_available_condition:
31+
print(f"{int(time.time())}: {name} has arrived at the bank.")
32+
customer_queue.append(name)
33+
34+
customer_available_condition.notify()
35+
36+
37+
customer_names = [
38+
"Customer 1",
39+
"Customer 2",
40+
"Customer 3",
41+
"Customer 4",
42+
"Customer 5",
43+
]
44+
45+
with ThreadPoolExecutor(max_workers=6) as executor:
46+
47+
teller_thread = executor.submit(serve_customers)
48+
49+
for name in customer_names:
50+
# Simulate customers arriving at random intervals
51+
time.sleep(random.randint(2, 5))
52+
53+
executor.submit(add_customer_to_queue, name)

thread-safety-locks/bank_deadlock.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import threading
2+
import time
3+
from concurrent.futures import ThreadPoolExecutor
4+
5+
6+
class BankAccount:
7+
def __init__(self):
8+
self.balance = 0
9+
self.lock = threading.Lock()
10+
11+
def deposit(self, amount):
12+
print(
13+
f"Thread {threading.current_thread().name} waiting "
14+
f"to acquire lock for deposit()"
15+
)
16+
with self.lock:
17+
print(
18+
f"Thread {threading.current_thread().name} "
19+
"acquired lock for deposit()"
20+
)
21+
time.sleep(0.1)
22+
self._update_balance(amount)
23+
24+
def _update_balance(self, amount):
25+
print(
26+
f"Thread {threading.current_thread().name} waiting to acquire "
27+
f"lock for _update_balance()"
28+
)
29+
with self.lock: # This will cause a deadlock
30+
print(
31+
f"Thread {threading.current_thread().name} "
32+
"acquired lock for _update_balance()"
33+
)
34+
self.balance += amount
35+
36+
37+
account = BankAccount()
38+
39+
40+
def make_deposit():
41+
account.deposit(100)
42+
43+
44+
with ThreadPoolExecutor(
45+
max_workers=3, thread_name_prefix="Worker"
46+
) as executor:
47+
for _ in range(3):
48+
executor.submit(make_deposit)
49+
50+
51+
print(f"Final balance: {account.balance}")

thread-safety-locks/bank_event.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import threading
2+
import time
3+
from concurrent.futures import ThreadPoolExecutor
4+
5+
bank_open = threading.Event()
6+
transactions_open = threading.Event()
7+
8+
9+
def serve_customer(customer_data):
10+
print(f"{customer_data['name']} is waiting for the bank to open.")
11+
12+
bank_open.wait()
13+
print(f"{customer_data['name']} entered the bank")
14+
if customer_data["type"] == "WITHDRAW_MONEY":
15+
print(f"{customer_data['name']} is waiting for transactions to open.")
16+
transactions_open.wait()
17+
print(f"{customer_data['name']} is starting their transaction.")
18+
19+
# Simulate the time taken for performing the transaction
20+
time.sleep(2)
21+
22+
print(f"{customer_data['name']} completed transaction and exited bank")
23+
else:
24+
# Simulate the time taken for banking
25+
time.sleep(2)
26+
print(f"{customer_data['name']} has exited bank")
27+
28+
29+
customers = [
30+
{"name": "Customer 1", "type": "WITHDRAW_MONEY"},
31+
{"name": "Customer 2", "type": "CHECK_BALANCE"},
32+
{"name": "Customer 3", "type": "WITHDRAW_MONEY"},
33+
{"name": "Customer 4", "type": "WITHDRAW_MONEY"},
34+
]
35+
36+
with ThreadPoolExecutor(max_workers=4) as executor:
37+
for customer_data in customers:
38+
executor.submit(serve_customer, customer_data)
39+
40+
print("Bank manager is preparing to open the bank.")
41+
time.sleep(2)
42+
print("Bank is now open!")
43+
bank_open.set() # Signal that the bank is open
44+
45+
time.sleep(3)
46+
print("Transactions are now open!")
47+
transactions_open.set()
48+
49+
50+
print("All customers have completed their transactions.")
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import time
2+
from concurrent.futures import ThreadPoolExecutor
3+
4+
5+
class BankAccount:
6+
def __init__(self):
7+
self.balance = 1000
8+
9+
def withdraw(self, amount):
10+
if self.balance >= amount:
11+
new_balance = self.balance - amount
12+
time.sleep(0.1) # Simulate a delay
13+
self.balance = new_balance
14+
else:
15+
raise Exception("Insufficient balance")
16+
17+
18+
account = BankAccount()
19+
20+
with ThreadPoolExecutor(max_workers=2) as executor:
21+
executor.submit(account.withdraw, 500)
22+
executor.submit(account.withdraw, 700)
23+
24+
print(f"Final account balance: {account.balance}")

thread-safety-locks/bank_rlock.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import threading
2+
import time
3+
from concurrent.futures import ThreadPoolExecutor
4+
5+
6+
class BankAccount:
7+
def __init__(self):
8+
self.balance = 0
9+
self.lock = threading.RLock()
10+
11+
def deposit(self, amount):
12+
print(
13+
f"Thread {threading.current_thread().name} "
14+
"waiting to acquire lock for .deposit()"
15+
)
16+
with self.lock:
17+
print(
18+
f"Thread {threading.current_thread().name} "
19+
"acquired lock for .deposit()"
20+
)
21+
time.sleep(0.1)
22+
self._update_balance(amount)
23+
24+
def _update_balance(self, amount):
25+
print(
26+
f"Thread {threading.current_thread().name} "
27+
"waiting to acquire lock for ._update_balance()"
28+
)
29+
with self.lock:
30+
print(
31+
f"Thread {threading.current_thread().name} "
32+
"acquired lock for ._update_balance()"
33+
)
34+
self.balance += amount
35+
36+
37+
account = BankAccount()
38+
39+
40+
def make_deposit():
41+
account.deposit(100)
42+
43+
44+
with ThreadPoolExecutor(
45+
max_workers=3, thread_name_prefix="Worker"
46+
) as executor:
47+
for _ in range(3):
48+
executor.submit(make_deposit)
49+
50+
51+
print(f"Final balance: {account.balance}")

thread-safety-locks/bank_semaphore.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import random
2+
import threading
3+
import time
4+
from concurrent.futures import ThreadPoolExecutor
5+
6+
# Semaphore with a maximum of 2 resources (tellers)
7+
teller_semaphore = threading.Semaphore(2)
8+
9+
10+
def serve_customer(name):
11+
print(f"{int(time.time())}: {name} is waiting for a teller.")
12+
with teller_semaphore:
13+
print(f"{int(time.time())}: {name} is being served by a teller.")
14+
# Simulate the time taken for the teller to serve the customer
15+
time.sleep(random.randint(1, 3))
16+
print(f"{int(time.time())}: {name} is done being served.")
17+
18+
19+
customers = [
20+
"Customer 1",
21+
"Customer 2",
22+
"Customer 3",
23+
"Customer 4",
24+
"Customer 5",
25+
]
26+
27+
with ThreadPoolExecutor(max_workers=5) as executor:
28+
for customer_name in customers:
29+
thread = executor.submit(serve_customer, customer_name)
30+
31+
32+
print("All customers have been served.")
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import threading
2+
import time
3+
from concurrent.futures import ThreadPoolExecutor
4+
5+
6+
class BankAccount:
7+
def __init__(self, balance=0):
8+
self.balance = balance
9+
self.account_lock = threading.Lock()
10+
11+
def withdraw(self, amount):
12+
with self.account_lock:
13+
if self.balance >= amount:
14+
new_balance = self.balance - amount
15+
print(f"Withdrawing {amount}...")
16+
time.sleep(0.1) # Simulate a delay
17+
self.balance = new_balance
18+
else:
19+
raise Exception("Insufficient balance")
20+
21+
def deposit(self, amount):
22+
with self.account_lock:
23+
new_balance = self.balance + amount
24+
print(f"Depositing {amount}...")
25+
time.sleep(0.1) # Simulate a delay
26+
self.balance = new_balance
27+
28+
29+
account = BankAccount(1000)
30+
31+
with ThreadPoolExecutor(max_workers=3) as executor:
32+
33+
executor.submit(account.withdraw, 700)
34+
executor.submit(account.deposit, 1000)
35+
executor.submit(account.withdraw, 300)
36+
37+
38+
print(f"Final account balance: {account.balance}")
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import threading
2+
import time
3+
from concurrent.futures import ThreadPoolExecutor
4+
5+
6+
def threaded_function():
7+
for number in range(3):
8+
print(f"Printing from {threading.current_thread().name}. {number=}")
9+
time.sleep(0.1)
10+
11+
12+
with ThreadPoolExecutor(
13+
max_workers=4, thread_name_prefix="Worker"
14+
) as executor:
15+
for _ in range(4):
16+
executor.submit(threaded_function)

0 commit comments

Comments
 (0)
0