Skip to content

Commit

Permalink
Merge branch 'master' into python-variables-update
Browse files Browse the repository at this point in the history
  • Loading branch information
brendaweles authored Oct 17, 2024
2 parents df1e22d + 754c56e commit 7d14b80
Show file tree
Hide file tree
Showing 14 changed files with 403 additions and 20 deletions.
4 changes: 2 additions & 2 deletions python-closure/roots.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ def root_calculator(number):
square_root = make_root_calculator(2, 4)
print(square_root(42))

cubic_root = make_root_calculator(3, 2)
cubic_root = make_root_calculator(3)
print(cubic_root(42))


Expand All @@ -24,5 +24,5 @@ def __call__(self, number):
square_root = RootCalculator(2, 4)
print(square_root(42))

cubic_root = RootCalculator(3, 2)
cubic_root = RootCalculator(3)
print(cubic_root(42))
11 changes: 11 additions & 0 deletions thread-safety-locks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Python Thread Safety: Using a Lock and Other Techniques

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/).

## About the Author

Adarsh Divakaran - Website: https://adarshd.dev/

## License

Distributed under the MIT license. See ``LICENSE`` for more information.
31 changes: 31 additions & 0 deletions thread-safety-locks/bank_barrier.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import random
import threading
import time
from concurrent.futures import ThreadPoolExecutor

teller_barrier = threading.Barrier(3)


def now():
return time.strftime("%H:%M:%S")


def prepare_for_work(name):
print(f"{now()}: {name} is preparing their counter.")

# Simulate the delay to prepare the counter
time.sleep(random.randint(1, 3))
print(f"{now()}: {name} has finished preparing.")

# Wait for all tellers to finish preparing
teller_barrier.wait()
print(f"{now()}: {name} is now ready to serve customers.")


tellers = ["Teller 1", "Teller 2", "Teller 3"]

with ThreadPoolExecutor(max_workers=3) as executor:
for teller_name in tellers:
executor.submit(prepare_for_work, teller_name)

print(f"{now()}: All tellers are ready to serve customers.")
54 changes: 54 additions & 0 deletions thread-safety-locks/bank_condition.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import random
import threading
import time
from concurrent.futures import ThreadPoolExecutor

customer_available_condition = threading.Condition()

# Customers waiting to be served by the Teller
customer_queue = []


def now():
return time.strftime("%H:%M:%S")


def serve_customers():
while True:
with customer_available_condition:
# Wait for a customer to arrive
while not customer_queue:
print(f"{now()}: Teller is waiting for a customer.")
customer_available_condition.wait()

# Serve the customer
customer = customer_queue.pop(0)
print(f"{now()}: Teller is serving {customer}.")

# Simulate the time taken to serve the customer
time.sleep(random.randint(1, 5))
print(f"{now()}: Teller has finished serving {customer}.")


def add_customer_to_queue(name):
with customer_available_condition:
print(f"{now()}: {name} has arrived at the bank.")
customer_queue.append(name)

customer_available_condition.notify()


customer_names = [
"Customer 1",
"Customer 2",
"Customer 3",
"Customer 4",
"Customer 5",
]

with ThreadPoolExecutor(max_workers=6) as executor:
teller_thread = executor.submit(serve_customers)
for name in customer_names:
# Simulate customers arriving at random intervals
time.sleep(random.randint(1, 3))
executor.submit(add_customer_to_queue, name)
46 changes: 46 additions & 0 deletions thread-safety-locks/bank_deadlock.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import threading
import time
from concurrent.futures import ThreadPoolExecutor


class BankAccount:
def __init__(self):
self.balance = 0
self.lock = threading.Lock()

def deposit(self, amount):
print(
f"Thread {threading.current_thread().name} waiting "
"to acquire lock for deposit()"
)
with self.lock:
print(
f"Thread {threading.current_thread().name} "
"acquired lock for deposit()"
)
time.sleep(0.1)
self._update_balance(amount)

def _update_balance(self, amount):
print(
f"Thread {threading.current_thread().name} waiting to acquire "
"lock for _update_balance()"
)
with self.lock: # This will cause a deadlock
print(
f"Thread {threading.current_thread().name} "
"acquired lock for _update_balance()"
)
self.balance += amount


account = BankAccount()

with ThreadPoolExecutor(
max_workers=3, thread_name_prefix="Worker"
) as executor:
for _ in range(3):
executor.submit(account.deposit, 100)


print(f"Final balance: {account.balance}")
50 changes: 50 additions & 0 deletions thread-safety-locks/bank_event.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import threading
import time
from concurrent.futures import ThreadPoolExecutor

bank_open = threading.Event()
transactions_open = threading.Event()


def serve_customer(customer_data):
print(f"{customer_data['name']} is waiting for the bank to open.")

bank_open.wait()
print(f"{customer_data['name']} entered the bank")
if customer_data["type"] == "WITHDRAW_MONEY":
print(f"{customer_data['name']} is waiting for transactions to open.")
transactions_open.wait()
print(f"{customer_data['name']} is starting their transaction.")

# Simulate the time taken for performing the transaction
time.sleep(2)

print(f"{customer_data['name']} completed transaction and exited bank")
else:
# Simulate the time taken for banking
time.sleep(2)
print(f"{customer_data['name']} has exited bank")


customers = [
{"name": "Customer 1", "type": "WITHDRAW_MONEY"},
{"name": "Customer 2", "type": "CHECK_BALANCE"},
{"name": "Customer 3", "type": "WITHDRAW_MONEY"},
{"name": "Customer 4", "type": "WITHDRAW_MONEY"},
]

with ThreadPoolExecutor(max_workers=4) as executor:
for customer_data in customers:
executor.submit(serve_customer, customer_data)

print("Bank manager is preparing to open the bank.")
time.sleep(2)
print("Bank is now open!")
bank_open.set() # Signal that the bank is open

time.sleep(3)
print("Transactions are now open!")
transactions_open.set()


print("All customers have completed their transactions.")
24 changes: 24 additions & 0 deletions thread-safety-locks/bank_multithreaded_withdrawal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import time
from concurrent.futures import ThreadPoolExecutor


class BankAccount:
def __init__(self):
self.balance = 1000

def withdraw(self, amount):
if self.balance >= amount:
new_balance = self.balance - amount
time.sleep(0.1) # Simulate a delay
self.balance = new_balance
else:
raise ValueError("Insufficient balance")


account = BankAccount()

with ThreadPoolExecutor(max_workers=2) as executor:
executor.submit(account.withdraw, 500)
executor.submit(account.withdraw, 700)

print(f"Final account balance: {account.balance}")
46 changes: 46 additions & 0 deletions thread-safety-locks/bank_rlock.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import threading
import time
from concurrent.futures import ThreadPoolExecutor


class BankAccount:
def __init__(self):
self.balance = 0
self.lock = threading.RLock()

def deposit(self, amount):
print(
f"Thread {threading.current_thread().name} "
"waiting to acquire lock for .deposit()"
)
with self.lock:
print(
f"Thread {threading.current_thread().name} "
"acquired lock for .deposit()"
)
time.sleep(0.1)
self._update_balance(amount)

def _update_balance(self, amount):
print(
f"Thread {threading.current_thread().name} "
"waiting to acquire lock for ._update_balance()"
)
with self.lock:
print(
f"Thread {threading.current_thread().name} "
"acquired lock for ._update_balance()"
)
self.balance += amount


account = BankAccount()

with ThreadPoolExecutor(
max_workers=3, thread_name_prefix="Worker"
) as executor:
for _ in range(3):
executor.submit(account.deposit, 100)


print(f"Final balance: {account.balance}")
36 changes: 36 additions & 0 deletions thread-safety-locks/bank_semaphore.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import random
import threading
import time
from concurrent.futures import ThreadPoolExecutor

# Semaphore with a maximum of 2 resources (tellers)
teller_semaphore = threading.Semaphore(2)


def now():
return time.strftime("%H:%M:%S")


def serve_customer(name):
print(f"{now()}: {name} is waiting for a teller.")
with teller_semaphore:
print(f"{now()}: {name} is being served by a teller.")
# Simulate the time taken for the teller to serve the customer
time.sleep(random.randint(1, 3))
print(f"{now()}: {name} is done being served.")


customers = [
"Customer 1",
"Customer 2",
"Customer 3",
"Customer 4",
"Customer 5",
]

with ThreadPoolExecutor(max_workers=5) as executor:
for customer_name in customers:
thread = executor.submit(serve_customer, customer_name)


print(f"{now()}: All customers have been served.")
37 changes: 37 additions & 0 deletions thread-safety-locks/bank_thread_safe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import threading
import time
from concurrent.futures import ThreadPoolExecutor


class BankAccount:
def __init__(self, balance=0):
self.balance = balance
self.account_lock = threading.Lock()

def withdraw(self, amount):
with self.account_lock:
if self.balance >= amount:
new_balance = self.balance - amount
print(f"Withdrawing {amount}...")
time.sleep(0.1) # Simulate a delay
self.balance = new_balance
else:
raise ValueError("Insufficient balance")

def deposit(self, amount):
with self.account_lock:
new_balance = self.balance + amount
print(f"Depositing {amount}...")
time.sleep(0.1) # Simulate a delay
self.balance = new_balance


account = BankAccount(1000)

with ThreadPoolExecutor(max_workers=3) as executor:
executor.submit(account.withdraw, 700)
executor.submit(account.deposit, 1000)
executor.submit(account.withdraw, 300)


print(f"Final account balance: {account.balance}")
16 changes: 16 additions & 0 deletions thread-safety-locks/threading_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import threading
import time
from concurrent.futures import ThreadPoolExecutor


def threaded_function():
for number in range(3):
print(f"Printing from {threading.current_thread().name}. {number=}")
time.sleep(0.1)


with ThreadPoolExecutor(
max_workers=4, thread_name_prefix="Worker"
) as executor:
for _ in range(4):
executor.submit(threaded_function)
Loading

0 comments on commit 7d14b80

Please sign in to comment.