Python 修复共享内存问题和锁定共享资源问题
本篇文章解释了多处理共享内存的不同方面,并演示了如何使用共享内存解决问题。 我们还将学习如何使用锁来锁定 Python 中的共享资源。
使用 multiprocessing.Array() 在 Python 中使用共享内存
当您有多个子进程时,多处理最关键的方面之一是在进程之间共享数据。
您使用处理模块的能力创建的子进程的基本属性之一是它们独立运行并拥有自己的内存空间。
这意味着孩子的进程将有一些内存空间。 并且,任何变量试图在其自己的内存空间中创建或将被更改,而不是在其父级的内存空间中。
让我们通过一个例子来尝试理解这个概念,并通过导入多处理模块进入代码。
我们创建了一个名为 RESULT 的空列表,并定义了一个名为 Make_Sqaured_List() 的函数,该函数对给定列表的元素进行平方并将它们附加到我们的全局 RESULT 列表中。
Procc_1 对象等于 Process()
类,并将目标设置为不带括号的 Make_Sqaured_List 函数。
并且,对于 args 参数,我们传递一个名为 Items_list 的单独列表,该列表将作为参数提供给 Make_Sqaured_List() 函数。
示例代码:
import multiprocessing
RESULT = []
def Make_Sqaured_List(Num_List):
global RESULT
for n in Num_List:
RESULT.append(n **2)
print(f"Result: {RESULT}")
if __name__ == '__main__':
Items_list = [5,6,7,8]
Procc_1 = multiprocessing.Process(target=Make_Sqaured_List, args=(Items_list,))
Procc_1.start()
Procc_1.join()
让我们执行这个子进程,我们根据我们的子进程得到的结果是全局列表的值。
但是,如果我们尝试打印仍然为空的 RESULT 列表,那么 RESULT 列表会发生什么情况?
print(RESULT)
输出:
[]
从我们的主进程来看,我们的父进程还是空的,而从子进程来看,RESULT列表是有内容的。 这只是意味着我们的不同进程都有不同的内存空间。
我们可以通过一个场景来理解这一点,在这个场景中我们有一个进程,这是我们的主程序,我们最初有一个空的 RESULT 列表。
而且,当我们创建一个子进程时,它最初也是一个空的,然后执行 Make_Sqaured_List()
函数,因此 RESULT 列表包含一些项目。 但是,由于我们正在从该内存空间中的父进程访问 RESULT,因此更改是不可见的。
示例代码:
import multiprocessing
RESULT = []
def Make_Sqaured_List(Num_List):
global RESULT
for n in Num_List:
RESULT.append(n **2)
print(f"Result: {RESULT}")
if __name__ == '__main__':
Items_list = [5,6,7,8]
Procc_1 = multiprocessing.Process(target=Make_Sqaured_List, args=(Items_list,))
Procc_1.start()
Procc_1.join()
print(RESULT)
输出:
那么,解决这个问题的方法是什么? 但是,首先,让我们看看如何解决它。
解决多进程之间共享数据问题的解决方案
在本节中,我们将看到帮助我们获取任何更改的值并解决多进程之间共享数据问题的解决方案。
该解决方案称为共享内存。 多处理模块为我们提供了两种类型的对象,称为数组和值,可用于在进程之间共享数据。
Array是从共享内存中分配的数组; 基本上,您的计算机内存中有一部分我们可以称为共享内存或多个进程可以访问的区域。
因此,在该共享内存中,我们创建了一个新数组或一个新值。 这些附加值不是我们的基本 Python 数据结构; multiprocessing 模块本身有一些不同和定义的东西。
现在我们使用 multiprocessing.Array()
声明一个名为 RESULT_ARRAY 的对象。 然后,在这个数组中,我们必须传递数据类型。 我们将 i 作为字符串传递,这意味着我们将在其中放入整数值,并且我们必须给出大小。
示例代码:
RESULT_ARRAY= multiprocessing.Array('i', 4)
它与 C 编程风格的数组有关,因此我们可以同时给出大小。 这样,我们就可以将对象存储在所需的位置。
现在我们正在创建一个名为 OBJ_Sum 的新值,它等于 multiprocessing.Value()
,它将存储并输入该值。
示例代码:
OBJ_Sum = multiprocessing.Value('i')
接下来,我们将创建一个名为 procc_1 的对象,它将等于 multiprocessing.Process()
,我们将调用一个函数。 我们创建了一个名为 Make_Sqaured_List()
的函数,它将采用三个参数:
- list
- array 对象
- value 对象。
我们将使用名为 args 的 Process 参数将这三个参数传递给我们的函数。 例如,看看下面的代码片段。
示例代码:
Procc_1 = multiprocessing.Process(target=Make_Sqaured_List, args=(Items_list, RESULT_ARRAY, OBJ_Sum))
现在在 Make_Sqaured_List()
函数中,我们将使用 enumerate()
函数迭代 Items_list。 这样我们就可以得到Items_list的索引和值了。
它是一个 C 风格的数组,所以我们必须使用索引将值分配给我们的数组。 我们还将对数组的值求和,OBJ_Sum.value 是 multiprocessing.Value() 的一个属性。
示例代码:
def Make_Sqaured_List(Items_list, RESULT, OBJ_Sum):
for i, n in enumerate(Items_list):
RESULT[i] = n **2
OBJ_Sum.value = sum(RESULT)
我们在主进程中定义了一些变量,改变了子进程调用的函数。 所以我们的主要议程是我们是否可以在我们的主要流程中获得那些改变的价值。
现在我们可以访问反映在子进程中的数组并使用 OBJ_Sum.value 获取其总和。 例如,请参见以下代码片段。
示例代码:
import multiprocessing
def Make_Sqaured_List(Items_list, RESULT, OBJ_Sum):
for i, n in enumerate(Items_list):
RESULT[i] = n **2
OBJ_Sum.value = sum(RESULT)
if __name__ == '__main__':
Items_list = [5,6,7,8]
RESULT_ARRAY= multiprocessing.Array('i', 4)
OBJ_Sum = multiprocessing.Value('i')
Procc_1 = multiprocessing.Process(target=Make_Sqaured_List, args=(Items_list, RESULT_ARRAY, OBJ_Sum))
Procc_1.start()
Procc_1.join()
print(RESULT_ARRAY[:])
print(OBJ_Sum.value)
输出:
这样,我们可以对父进程中定义的对象以及我们从子进程返回的那些更改进行任何更改。 这可以通过使用共享内存技术来实现。
使用 multiprocessing.Lock() 锁定 Python 中的共享资源
我们将讨论一个名为锁的重要主题; 现在,如果您上过计算机科学或操作系统课程,那么您已经了解了锁。 但是,当涉及到多处理和操作系统概念时,锁是一个关键概念。
首先,让我们考虑一下现实生活中为什么需要锁; 在我们的日常生活中,有些资源不能被两个人同时访问。
例如,浴室的门有锁,因为如果两个人同时试图打开它,就会造成相当尴尬的局面。 这就是我们保护浴室的原因,这是一个带锁的共享资源。
同样,在编程世界中,每当两个进程或线程试图访问一个共享资源,例如共享内存文件或数据库时。 它会产生问题,因此您必须使用锁来保护该访问。
如果我们不将这种保护添加到我们的程序中会发生什么,我们将通过运行示例来了解。 同样,这是一个银行软件程序,这里有两个进程。
第一个过程是使用 MONEY_DP()
函数将钱存入银行,第二个过程是使用 MONEY_WD()
函数从银行取款。 最后,我们正在打印最终余额。
我们从 MONEY_DP 部分的 200 美元余额开始。 我们存入 100 美元,我们有一个跟踪 100 次的 for 循环,在每次迭代中,它都会将 01 美元添加到我们的银行账户中。
同样,在 MONEY_WD 函数中,我们将相同的循环迭代 100 次,每次都会从我们的银行账户中扣除 1 美元。
示例代码:
import multiprocessing
import time
def MONEY_DP(B):
for i in range(100):
time.sleep(0.01)
B.value = B.value + 1
def MONEY_WD(B):
for i in range(100):
time.sleep(0.01)
B.value = B.value - 1
现在我们正在使用一个名为 value 的共享内存变量,我们已经在上一节中了解过它。 这个 multiprocessing 值是一个共享内存资源,所以让我们看看当我们尝试运行这个程序时会发生什么。
if __name__ == '__main__':
B = multiprocessing.Value('i', 200)
Deposit = multiprocessing.Process(target=MONEY_DP, args=(B,))
Withdrawl = multiprocessing.Process(target=MONEY_WD, args=(B,))
Deposit.start()
Withdrawl.start()
Deposit.join()
Withdrawl.join()
print(B.value)
我们将多次运行它,每次,它只是打印不同的值,但它应该打印 200 美元。
输出:
# execution 1
205
# execution 2
201
# execution 3
193
为什么会这样? 它主要发生在该进程试图读取共享内存中名为 B.value 的变量时。
假设 B.value 有一个 200$ 的值,它会读取它,然后加一,然后它会把同样的东西放回同一个变量中。
由于 B.value 是 200$ 并且它是在操作系统级别执行此加法操作,因此在操作系统级别,它将执行多个流水线指令。
所以我们读取了变量 200; 它被加一,并将 201 分配回 B.value 变量。
示例代码:
B.value = B.value + 1
现在,当它同时执行时,该指令也在 MONEY_WD()
函数中执行。
示例代码:
B.value = B.value - 1
虽然我们是先存后取,但是当进程试图读取B.value时,它仍然是200,因为存入过程还没有写回原来的变量。
而不是在 MONEY_WD 进程中将 B.value 读取为 201,它将读取 B.value 为 200,并且在减少 1 之后,它将有 199。
这就是为什么我们会出现不一致的行为。 首先,让我们使用 Lock 来锁定访问; 现在,我们创建一个名为 lock 的变量,并使用多处理模块来使用 Lock 类。
示例代码:
lock=multiprocessing.Lock()
现在我们将该锁传递给两个进程,在两个进程内,我们将调用 lock.acquire()
来放置锁,然后释放锁,我们将调用 lock.release()
函数。
这些锁函数在访问共享资源时保护代码段,称为临界区。
示例代码:
import multiprocessing
import time
def MONEY_DP(B,lock):
for i in range(100):
time.sleep(0.01)
lock.acquire()
B.value = B.value + 1
lock.release()
def MONEY_WD(B,lock):
for i in range(100):
time.sleep(0.01)
lock.acquire()
B.value = B.value - 1
lock.release()
if __name__ == '__main__':
B = multiprocessing.Value('i', 200)
lock=multiprocessing.Lock()
Deposit = multiprocessing.Process(target=MONEY_DP, args=(B,lock))
Withdrawl = multiprocessing.Process(target=MONEY_WD, args=(B,lock))
Deposit.start()
Withdrawl.start()
Deposit.join()
Withdrawl.join()
print(B.value)
现在,这段代码每次都打印 200。
输出:
200
PS C:\Users\Dell\Desktop\demo> python -u "c:\Users\Dell\Desktop\demo\demo.py"
200
PS C:\Users\Dell\Desktop\demo> python -u "c:\Users\Dell\Desktop\demo\demo.py"
200
相关文章
Python 中 ImportError: No Module Named Setuptools 错误
发布时间:2023/05/15 浏览次数:140 分类:Python
-
本篇文章讨论了 ImportError saying no module named setuptools 并提供了在 Python 中消除此错误的解决方案。Python 设置工具库
Python 中解决 TypeError: Can't Multiply Sequence by Non-Int of Type STR 错误
发布时间:2023/05/15 浏览次数:189 分类:Python
-
本篇文章着重于并旨在为 TypeError: can't multiply sequence by non-int of type 'str' 错误提供解决方案。Python 提供了多种数据类型,str 数据类型就是其中之一。 Python 中的字符串表示一系列 Unicode 字符,用
Python 中 NameError: The OS Module Is Not Defined 错误
发布时间:2023/05/15 浏览次数:111 分类:Python
-
os 模块提供了使我们能够与操作系统交互的函数和依赖项。如果在没有先导入os模块的情况下使用os模块函数,会导致错误,即NameError: the OS module is not defined in Python。
Windows 10 无法识别 Python
发布时间:2023/05/15 浏览次数:119 分类:Python
-
我们将介绍如何解决 Windows 10 上“python”无法识别的错误。修复“python”在 Windows 10 中无法识别的错误
Python 中 Handling Socket.Error: [Errno 104] Connection Reset by Peer 错误
发布时间:2023/05/15 浏览次数:183 分类:Python
-
在我们创建连接到服务器以下载信息(如外汇或比特币汇率)的应用程序后,我们往往会遇到各种连接问题; 其中一个问题是 [Errno 104] Connection reset by peer 错误。
Python 中 TypeError: Can't Convert 'List' Object to STR 错误
发布时间:2023/05/15 浏览次数:125 分类:Python
-
我们将使用示例将列表对象转换为 Python 中的字符串。 我们还将通过示例介绍如何在 Python 中将字符串与列表对象连接起来。
Python 中 TypeError: 'DataFrame' Object Is Not Callable 错误
发布时间:2023/05/15 浏览次数:166 分类:Python
-
我们将介绍如何在 Python 中基于查询从 DataFrame 中调用数据。 我们还将通过示例介绍如何解决Python中的错误 TypeError: ‘DataFrame’ object is not callable 。
Python 中 PermissionError: [WinError 5] Access Is Denied 错误
发布时间:2023/05/13 浏览次数:166 分类:Python
-
我们将简单介绍如何解决 PermissionError: [WinError 5] Access is denied in Python 。PermissionError: [WinError 5] Python 拒绝访问
Python 中 ZeroDivisionError: Float Division by Zero 错误
发布时间:2023/05/13 浏览次数:123 分类:Python
-
我们将介绍为什么会出现 ZeroDivisionError 以及我们如何通过 Python 中的示例轻松解决它。Python ZeroDivisionError: Float Division by Zero