Python 线程 - 关键部分

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/419145/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 20:03:47  来源:igfitidea点击:

Python Threads - Critical Section

pythonmultithreadinglanguage-agnostic

提问by Devoted

What is the "critical section" of a thread (in Python)?

线程的“临界区”是什么(在 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.

线程通过调用acquire() 方法进入临界区,该方法可以是阻塞的也可以是非阻塞的。线程通过调用 release() 方法退出临界区。

- Understanding Threading in Python, Linux Gazette

-了解 Python 中的线程,Linux Gazette

Also, what is the purpose of a lock?

另外,锁的用途是什么?

回答by cdleary

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:

假设+=运算符由三个子组件组成:

  • Read the current value
  • Add the RHS to that value
  • Write the accumulated value back to the LHS (technically bindit in Python terms)
  • 读取当前值
  • 将 RHS 添加到该值
  • 将累积值写回 LHS(技术上用 Python 术语绑定它)

If you don't have the with account_balance_lockstatement and you execute two change_account_balancecalls 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:

如果您没有该with account_balance_lock语句并且change_account_balance并行执行两个调用,则最终可能会以危险的方式交错执行三个子组件操作。假设您同时呼叫change_account_balance(100)(AKA pos) 和change_account_balance(-100)(AKA neg)。这可能发生:

pos = threading.Thread(target=change_account_balance, args=[100])
neg = threading.Thread(target=change_account_balance, args=[-100])
pos.start(), neg.start()
  • pos: read current value -> 0
  • neg: read current value -> 0
  • pos: add current value to read value -> 100
  • neg: add current value to read value -> -100
  • pos: write current value -> account_balance = 100
  • neg: write current value -> account_balance = -100
  • pos: 读取当前值 -> 0
  • 负:读取当前值 -> 0
  • pos:将当前值添加到读取值 -> 100
  • 负:将当前值添加到读取值 -> -100
  • pos:写入当前值 -> account_balance = 100
  • 否定:写入当前值 -> account_balance = -100

Because you didn't force the operations to happen in discrete chunks you can have three possible outcomes (-100, 0, 100).

因为您没有强制操作发生在离散块中,所以您可以得到三种可能的结果(-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_balanceare "thread-safe" (parallelism-safe).

with [lock]语句是一个单一的、不可分割的操作,它说:“让我成为执行此代码块的唯一线程。如果还有其他东西正在执行,那就太酷了——我会等待。” 这确保了对 的更新account_balance是“线程安全的”(并行安全)。

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_balancefor the code to remain thread-safe. There are ways to make this less fragile, but that's the answer to a whole other question.

注意:这个模式有一个警告:你必须记住每次想要操作时都获取account_balance_lock(via with)account_balance以使代码保持线程安全。有一些方法可以使它不那么脆弱,但这是另一个问题的答案。

Edit:In retrospect, it's probably important to mention that the withstatement implicitly calls a blocking acquireon 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.

编辑:回想起来,重要的是要提到该with语句隐式调用acquire锁定的阻塞——这是上述线程对话框的“我将等待”部分。相反,非阻塞获取表示,“如果我不能立即获取锁,请告诉我”,然后依靠您来检查您是否获得了锁。

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 acquireacross threads), which a simple boolean flag will not guarantee. The semantics of atomic operations are probably also the content of another question.

我还想补充一点,锁的主要目的是保证获取的原子性(acquire跨线程的不可分割性),这是一个简单的布尔标志不能保证的。原子操作的语义大概也是另一个问题的内容。

回答by zenazn

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.

代码的临界区是一次只能由一个线程执行的代码。以聊天服务器为例。如果每个连接(即每个最终用户)都有一个线程,则一个“关键部分”是假脱机代码(向所有客户端发送传入消息)。如果多个线程试图同时对一条消息进行假脱机处理,您会发现 BfrIToS MANtwD PIoEmesCEsaSges 交织在一起,这显然一点好处都没有。

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."

锁是一种可用于同步对临界区(或一般资源)的访问的东西。在我们的聊天服务器示例中,锁就像一个锁着的房间,里面有一台打字机。如果那里有一个线程(输入消息),则没有其他线程可以进入房间。第一个线程完成后,他打开房间并离开。然后另一个线程可以进入房间(锁定它)。“获得”锁只是意味着“我得到了房间”。

回答by Charlie Martin

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 writevalues into memory that can be shared among more than one concurrent process.

“临界区”是一个代码块,其中为了正确性,有必要确保只有一个控制线程可以是在一个时间该部分。通常,您需要一个临界区来包含值写入可以在多个并发进程之间共享的内存的引用。