了解 Python 中的多线程和锁(概念和示例)
Understanding multi-threading and locks in Python (concept and example)
我为使用它的编程项目研究了多线程(这里是初学者...)。如果您认为我的以下陈述是正确的,或者对错误或需要更正的陈述发表评论,我将不胜感激。
- 锁是一个可以通过引用传递给函数、方法...的对象。然后(在本例中)函数可以使用该锁定对象引用来安全地操作数据(在本例中为变量)。它通过获取锁、修改变量然后释放锁来实现。
- 可以创建一个线程来定位一个函数,该函数可能会获得对锁的引用(然后实现上述内容)。
- 锁不保护特定变量、对象等
- 锁不保护或做任何事情,除非它被获取(和释放)。
- 因此,程序员有责任使用锁以实现所需的保护。
- 如果在线程 A 执行的函数内部获取锁,这 对任何其他 运行 线程 B 没有直接影响。线程 A 和 B 引用了同一个锁对象。
- 仅当线程 B 的目标函数想要获取 相同的 锁(即通过相同的引用锁对象)时,该锁已被线程 A 的目标函数获取那时,锁会影响两个线程,因为线程 B 将暂停进一步执行,直到线程 A 所针对的函数再次释放锁。
- 因此,锁定的锁只会暂停线程的执行,如果它的目标函数想要(并等待)自己获取完全相同的锁。因此,线程A获取锁,只能阻止线程B获取同一个锁,仅此而已。
如果我想在设置变量时使用锁来防止竞争条件,我(作为程序员)需要:
- 将锁传递给所有 函数,这些函数的目标是要设置变量的线程和
- 在我设置变量之前每个函数和每次获取锁(然后释放它)。 (*)
- 如果我只创建一个针对函数的线程,而没有向它提供对锁定对象的引用并让它设置变量或
- 如果我通过目标函数具有锁定对象的线程设置变量,但在操作之前没有获取它,我将无法实现线程-变量的安全设置。
(*) 只要变量不能被其他线程访问,就应该获取锁。现在,我喜欢将其与数据库事务进行比较...我锁定数据库(~获取锁)直到我的指令集完成,然后我提交(~释放锁)。
示例如果我想创建一个class,其成员_value
应该在一个线程中set -安全时尚,我会实施这两个版本之一:
class Version1:
def __init__(self):
self._value:int = 0
self._lock:threading.Lock = threading.Lock()
def getValue(self) -> int:
"""Getting won't be protected in this example."""
return self._value
def setValue(self, val:int) -> None:
"""This will be made thread-safe by member lock."""
with self._lock:
self._value = val
v1 = Version1()
t1_1 = threading.Thread(target=v1.setValue, args=(1)).start()
t1_2 = threading.Thread(target=v1.setValue, args=(2)).start()
class Version2:
def __init__(self):
self._value:int = 0
def getValue(self) -> int:
"""Getting won't be protected in this example."""
return self._value
def setValue(self, val:int, lock:threading.Lock) -> None:
"""This will be made thread-safe by injected lock."""
with self._lock:
self._value = val
v2 = Version2()
l = threading.Lock()
t2_1 = threading.Thread(target=v2.setValue, args=(1, l)).start()
t2_2 = threading.Thread(target=v2.setValue, args=(2, l)).start()
- 在
Version1
中,我作为class提供者,可以保证设置_value
始终是线程安全的...
- ...因为在
Version2
中,我的 class 的用户可能会将不同的锁对象传递给两个生成的线程,从而使锁保护失效。
- 如果我想让 class 的用户自由地将
_value
的设置包含到应该以线程安全方式执行的更大的步骤集合中,我可以将 Lock
引用注入 Version1
的 __init__
函数并将其分配给 _lock
成员。因此,class 的线程安全操作将得到保证,同时仍然允许 class 的用户为此目的使用“她自己的”锁。
0-15 的分数现在将评估我对锁的(误解)理解程度...:-D
- 使用全局变量进行锁也很常见。这取决于锁保护的是什么。
- 是的,虽然有点无意义。任何函数都可以使用锁,而不仅仅是作为线程目标的函数。
- 如果您的意思是锁与其保护的数据之间没有直接 link,那是对的。但是您可以定义一个数据结构,其中包含需要保护的值和对其锁的引用。
- 没错。虽然如我在3中所说,你可以定义一个数据结构来封装数据和锁。您可以将其设为 class 并让 class 方法根据需要自动获取锁。
- 正确。但是请参阅 4 以了解如何自动执行此操作。
- 正确。
- 正确。
- 正确。
- 如果不是全局锁则更正。
- 部分正确。如果您只是 读取 变量,您还应该经常获取锁。如果读取对象不是原子的(例如,它是一个列表并且您正在读取多个元素,或者您读取同一个标量对象可变次数并期望它是稳定的),您需要防止另一个线程在您读取时修改它正在阅读。
- 正确。
- 正确。
- 正确。这是我在上面 3 和 4 中描述的示例。
- 正确。这就是为什么 13 中的设计通常更好。
- 这很棘手,因为锁定的粒度需要反映所有需要保护的对象。您的 class 仅保护该变量的赋值——它会在与 caller-provided 锁关联的所有其他步骤完成之前释放锁。
我为使用它的编程项目研究了多线程(这里是初学者...)。如果您认为我的以下陈述是正确的,或者对错误或需要更正的陈述发表评论,我将不胜感激。
- 锁是一个可以通过引用传递给函数、方法...的对象。然后(在本例中)函数可以使用该锁定对象引用来安全地操作数据(在本例中为变量)。它通过获取锁、修改变量然后释放锁来实现。
- 可以创建一个线程来定位一个函数,该函数可能会获得对锁的引用(然后实现上述内容)。
- 锁不保护特定变量、对象等
- 锁不保护或做任何事情,除非它被获取(和释放)。
- 因此,程序员有责任使用锁以实现所需的保护。
- 如果在线程 A 执行的函数内部获取锁,这 对任何其他 运行 线程 B 没有直接影响。线程 A 和 B 引用了同一个锁对象。
- 仅当线程 B 的目标函数想要获取 相同的 锁(即通过相同的引用锁对象)时,该锁已被线程 A 的目标函数获取那时,锁会影响两个线程,因为线程 B 将暂停进一步执行,直到线程 A 所针对的函数再次释放锁。
- 因此,锁定的锁只会暂停线程的执行,如果它的目标函数想要(并等待)自己获取完全相同的锁。因此,线程A获取锁,只能阻止线程B获取同一个锁,仅此而已。
如果我想在设置变量时使用锁来防止竞争条件,我(作为程序员)需要:
- 将锁传递给所有 函数,这些函数的目标是要设置变量的线程和
- 在我设置变量之前每个函数和每次获取锁(然后释放它)。 (*)
- 如果我只创建一个针对函数的线程,而没有向它提供对锁定对象的引用并让它设置变量或
- 如果我通过目标函数具有锁定对象的线程设置变量,但在操作之前没有获取它,我将无法实现线程-变量的安全设置。
(*) 只要变量不能被其他线程访问,就应该获取锁。现在,我喜欢将其与数据库事务进行比较...我锁定数据库(~获取锁)直到我的指令集完成,然后我提交(~释放锁)。
示例如果我想创建一个class,其成员_value
应该在一个线程中set -安全时尚,我会实施这两个版本之一:
class Version1:
def __init__(self):
self._value:int = 0
self._lock:threading.Lock = threading.Lock()
def getValue(self) -> int:
"""Getting won't be protected in this example."""
return self._value
def setValue(self, val:int) -> None:
"""This will be made thread-safe by member lock."""
with self._lock:
self._value = val
v1 = Version1()
t1_1 = threading.Thread(target=v1.setValue, args=(1)).start()
t1_2 = threading.Thread(target=v1.setValue, args=(2)).start()
class Version2:
def __init__(self):
self._value:int = 0
def getValue(self) -> int:
"""Getting won't be protected in this example."""
return self._value
def setValue(self, val:int, lock:threading.Lock) -> None:
"""This will be made thread-safe by injected lock."""
with self._lock:
self._value = val
v2 = Version2()
l = threading.Lock()
t2_1 = threading.Thread(target=v2.setValue, args=(1, l)).start()
t2_2 = threading.Thread(target=v2.setValue, args=(2, l)).start()
- 在
Version1
中,我作为class提供者,可以保证设置_value
始终是线程安全的... - ...因为在
Version2
中,我的 class 的用户可能会将不同的锁对象传递给两个生成的线程,从而使锁保护失效。 - 如果我想让 class 的用户自由地将
_value
的设置包含到应该以线程安全方式执行的更大的步骤集合中,我可以将Lock
引用注入Version1
的__init__
函数并将其分配给_lock
成员。因此,class 的线程安全操作将得到保证,同时仍然允许 class 的用户为此目的使用“她自己的”锁。
0-15 的分数现在将评估我对锁的(误解)理解程度...:-D
- 使用全局变量进行锁也很常见。这取决于锁保护的是什么。
- 是的,虽然有点无意义。任何函数都可以使用锁,而不仅仅是作为线程目标的函数。
- 如果您的意思是锁与其保护的数据之间没有直接 link,那是对的。但是您可以定义一个数据结构,其中包含需要保护的值和对其锁的引用。
- 没错。虽然如我在3中所说,你可以定义一个数据结构来封装数据和锁。您可以将其设为 class 并让 class 方法根据需要自动获取锁。
- 正确。但是请参阅 4 以了解如何自动执行此操作。
- 正确。
- 正确。
- 正确。
- 如果不是全局锁则更正。
- 部分正确。如果您只是 读取 变量,您还应该经常获取锁。如果读取对象不是原子的(例如,它是一个列表并且您正在读取多个元素,或者您读取同一个标量对象可变次数并期望它是稳定的),您需要防止另一个线程在您读取时修改它正在阅读。
- 正确。
- 正确。
- 正确。这是我在上面 3 和 4 中描述的示例。
- 正确。这就是为什么 13 中的设计通常更好。
- 这很棘手,因为锁定的粒度需要反映所有需要保护的对象。您的 class 仅保护该变量的赋值——它会在与 caller-provided 锁关联的所有其他步骤完成之前释放锁。