python multiprocessing多进程变量共享与加锁的实现

yipeiwu_com7年前Python基础

python多进程和多线程是大家会重点了解的部分,因为很多工作如果并没有前后相互依赖关系的话其实顺序并不是非常的重要,采用顺序执行的话就必定会造成无谓的等待,任凭cpu和内存白白浪费,这是我们不想看到的。

为了解决这个问题,我们就可以采用多线程或者多进程的方式,(多线程我们之后再讲),而这两者之间是有本质区别的。就内存而言,已知进程是在执行过程中有独立的内存单元的,而多个线程是共享内存的,这是多进程和多线程的一大区别。

利用Value在不同进程中同步变量

在多进程中,由于进程之间内存相互是隔离的,所以无法在多个进程中用直接读取的方式共享变量,这时候就可以用multiprocessing库中的 Value在各自隔离的进程中共享变量。

下面是一个多进程的例子:

假设有一个counter用来记录程序经过的总循环次数,每调用一次count函数之后counter就会增加20,在主程序中用循环开10个进程分别调用count函数,那么理想状态下,在十个进程中共享的counter值到程序结束后应该是200。

from multiprocessing import Process, Value
import time

def count(v):
  for i in range(20):
    time.sleep(0.01)
    v.value += 1

def main():
  value = Value('i',0)
  processes = [Process(target=count, args=(value,)) for i in range(10)]

  for p in processes:
    p.start()
  for p in processes:
    p.join()

  print(value.value)

if __name__ == '__main__':

  for i in range(10):
    main()

运行这个例子,会得到怎样的结果呢?

188
180
168
186
183
179
186
181
166
186

我在主程序里运行了十次这个程序,而最后的结果是160-180之间,总之,没有一次到200。这是什么原因呢?

相信很多人都已经明白了问题所在,那就是因为在multiprocessing库中的Value是细粒度的,Value中有一个ctypes类型的对象,拥有一个value属性来表征内存中实际的对象。Value可以保证同时只有一个单独的线程或进程在读或者写value值。这么看起来没有什么问题。

然而在第一个进程加载value值的时候,程序却不能阻止第二个进程加载旧的值。两个进程都会把value拷贝到自己的私有内存然后进行处理,并写回到共享值里。

那么这么会出现什么问题呢?

最后的共享值只接收到了一次值的增加,而非两次。

利用Lock在不同进程共享变量时加锁

上面的问题其实可以用一个非常简单的方法解决,我们只需要调用multiprocessing库中的Lock (锁)就可以保证一次只能有一个进程访问这个共享变量。修改后的代码如下:

from multiprocessing import Process, Value, Lock
from time import sleep

def count(x,lock):
  for i in range(20):
    sleep(0.01)
    with lock:
      x.value += 1


def main():
  counter = Value('i',0)
  lock = Lock()
  processes = [Process(target=count,args=(counter,lock)) for i in range(10)]
  for p in processes:
    p.start()
  for p in processes:
    p.join()

  print(counter.value)

if __name__ == '__main__':
  for i in range(10):
    main()

这样一来,输出的结果就会恒定为200了。

一些补充

1. 调用get_lock() 函数

其实Value这个包里已经包含了锁的概念,如果调用get_lock() 函数就可以自动给共享变量加锁。这样其实是比较推荐的方式,因为这样就不需要同时调用两个包。修改如下:

from multiprocessing import Process, Value
from time import sleep

def count(x):
  for i in range(20):
    global counter # 声明全局变量
    sleep(0.01)
    with counter.get_lock(): # 直接调用get_lock()函数获取锁
      x.value += 1

def main():
  processes = [Process(target=count, args=(counter,)) for i in range(10)]
  for p in processes:
    p.start()
  for p in processes:
    p.join()

  print(counter.value)

if __name__ == '__main__':
  counter = Value('i', 0) # 需要把全局变量移到主程序
  main()

上面的程序更加明确,且最终结果也是200。

2. 使用 multiprocessing.RawValue

整个multiprocessing包里刚刚调用的Value和Lock还可以统一被 multiprocessing.RawValue取代。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持【听图阁-专注于Python设计】。

相关文章

Python EOL while scanning string literal问题解决方法

项目中有个定时任务,每天取到一些表数据传到一个外部接口,但是最近总是有异常,今天查了下原因。 首先本地和测试环境测试这个程序都没问题,只有线上环境会在日志中抛出异常,猜测异常主要产生的原...

Python将多个excel表格合并为一个表格

Python将多个excel表格合并为一个表格

生活中经常会碰到多个excel表格汇总成一个表格的情况,比如你发放了一份表格让班级所有同学填写,而你负责将大家的结果合并成一个。诸如此类的问题有很多。除了人工将所有表格的内容一个一个复制...

Python和perl实现批量对目录下电子书文件重命名的代码分享

经常会遇到下载的文件或电子书,名字中间都包含了一些网址信息,实际使用中由于名字太长不方便,下面的脚本使用正则表达式来对目录下的所有文件重命名: 例如: 修改前:[【听图阁-专注于Pyth...

Python方法的延迟加载的示例代码

数据挖掘的过程中,数据进行处理是一重要的环节,我们往往会将其封装成一个方法,而有的时候这一个方法可能会被反复调用,每一次都对数据进行处理这将是一个很耗时耗资源的操纵,那么有没有办法将计算...

Python3.4学习笔记之列表、数组操作示例

本文实例讲述了Python3.4列表、数组操作。分享给大家供大家参考,具体如下: python列表,数组类型要相同,python不需要指定数据类型,可以把各种类型打包进去 python列...