What is the "critical section" of a thread (in Python)?
A thread enters the critical section by calling the acquire() method, which can either be blocking or non-blocking. A thread exits the critical section, by calling the release() method.
- Understanding Threading in Python, Linux Gazette
Also, what is the purpose of a lock?
A "critical section" is a chunk of code in which, for correctness, it is necessary to ensure that only one thread of control can be in that section at a time. In general, you need a critical section to contain references that write values into memory that can be shared among more than one concurrent process.
Python processes typically use a single thread because of the GIL. Despite the GIL, libraries that perform computationally heavy tasks like numpy, scipy and pytorch utilise C-based implementations under the hood, allowing the use of multiple cores.
A critical section is a section of code that is executed by multiple threads and where the sequence of execution for the threads makes a difference in the result of the concurrent execution of the critical section.
We can share a local variable between threads using a queue. The queue must be shared and accessible to each thread and within the function where the local variable is defined and used. For example, we can first create a queue. Queue instance.
Other people have given very nice definitions. Here's the classic example:
import threading
account_balance = 0 # The "resource" that zenazn mentions.
account_balance_lock = threading.Lock()
def change_account_balance(delta):
global account_balance
with account_balance_lock:
# Critical section is within this block.
account_balance += delta
Let's say that the +=
operator consists of three subcomponents:
If you don't have the with account_balance_lock
statement and you execute two change_account_balance
calls in parallel you can end up interleaving the three subcomponent operations in a hazardous manner. Let's say you simultaneously call change_account_balance(100)
(AKA pos) and change_account_balance(-100)
(AKA neg). This could happen:
pos = threading.Thread(target=change_account_balance, args=[100])
neg = threading.Thread(target=change_account_balance, args=[-100])
pos.start(), neg.start()
Because you didn't force the operations to happen in discrete chunks you can have three possible outcomes (-100, 0, 100).
The with [lock]
statement is a single, indivisible operation that says, "Let me be the only thread executing this block of code. If something else is executing, it's cool -- I'll wait." This ensures that the updates to the account_balance
are "thread-safe" (parallelism-safe).
Note: There is a caveat to this schema: you have to remember to acquire the account_balance_lock
(via with
) every time you want to manipulate the account_balance
for the code to remain thread-safe. There are ways to make this less fragile, but that's the answer to a whole other question.
Edit: In retrospect, it's probably important to mention that the with
statement implicitly calls a blocking acquire
on the lock -- this is the "I'll wait" part of the above thread dialog. In contrast, a non-blocking acquire says, "If I can't acquire the lock right away, let me know," and then relies on you to check whether you got the lock or not.
import logging # This module is thread safe.
import threading
LOCK = threading.Lock()
def run():
if LOCK.acquire(False): # Non-blocking -- return whether we got it
logging.info('Got the lock!')
LOCK.release()
else:
logging.info("Couldn't get the lock. Maybe next time")
logging.basicConfig(level=logging.INFO)
threads = [threading.Thread(target=run) for i in range(100)]
for thread in threads:
thread.start()
I also want to add that the lock's primary purpose is to guarantee the atomicity of acquisition (the indivisibility of the acquire
across threads), which a simple boolean flag will not guarantee. The semantics of atomic operations are probably also the content of another question.
A critical section of code is one that can only be executed by one thread at a time. Take a chat server for instance. If you have a thread for each connection (i.e., each end user), one "critical section" is the spooling code (sending an incoming message to all the clients). If more than one thread tries to spool a message at once, you'll get BfrIToS mANtwD PIoEmesCEsaSges intertwined, which is obviously no good at all.
A lock is something that can be used to synchronize access to a critical section (or resources in general). In our chat server example, the lock is like a locked room with a typewriter in it. If one thread is in there (to type a message out), no other thread can get into the room. Once the first thread is done, he unlocks the room and leaves. Then another thread can go in the room (locking it). "Aquiring" the lock just means "I get the room."
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With