关于我

一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android、Python、Java和Go,这个也是我们团队的主要技术栈。

Github:https://github.com/hylinux1024

微信公众号:终身开发者(angrycode)

0x00 使用进程实现并发

上一篇文章介绍了线程的使用。然而Python中由于Global Interpreter Lock(全局解释锁GIL)的存在,每个线程在在执行时需要获取到这个GIL,在同一时刻中只有一个线程得到解释锁的执行,Python中的线程并没有真正意义上的并发执行,多线程的执行效率也不一定比单线程的效率更高。

如果要充分利用现代多核CPU的并发能力,就要使用multipleprocessing模块了。

0x01 multipleprocessing

与使用线程的threading模块类似,multipleprocessing模块提供许多高级API。最常见的是Pool对象了,使用它的接口能很方便地写出并发执行的代码。

from multiprocessing import Pool

def f(x):
return x * x if __name__ == '__main__':
with Pool(5) as p:
# map方法的作用是将f()方法并发地映射到列表中的每个元素
print(p.map(f, [1, 2, 3])) # 执行结果
# [1, 4, 9]

关于Pool下文中还会提到,这里我们先来看Process

Process

要创建一个进程可以使用Process类,使用start()方法启动进程。

from multiprocessing import Process
import os def echo(text):
# 父进程ID
print("Process Parent ID : ", os.getppid())
# 进程ID
print("Process PID : ", os.getpid())
print('echo : ', text) if __name__ == '__main__':
p = Process(target=echo, args=('hello process',))
p.start()
p.join() # 执行结果
# Process Parent ID : 27382
# Process PID : 27383
# echo : hello process
进程池

正如开篇提到的multiprocessing模块提供了Pool类可以很方便地实现一些简单多进程场景。

它主要有以下接口

  • apply(func[, args[, kwds]])

    执行func(args,kwds)方法,在方法结束返回前会阻塞。
  • apply_async(func[, args[, kwds[, callback[, error_callback]]]])

    异步执行func(args,kwds),会立即返回一个result对象,如果指定了callback参数,结果会通过回调方法返回,还可以指定执行出错的回调方法error_callback()
  • map(func, iterable[, chunksize])

    类似内置函数map(),可以并发执行func,是同步方法
  • map_async(func, iterable[, chunksize[, callback[, error_callback]]])

    异步版本的map
  • close()

    关闭进程池。当池中的所有工作进程都执行完毕时,进程会退出。
  • terminate()

    终止进程池
  • join()

    等待工作进程执行完,必需先调用close()或者terminate()
from multiprocessing import Pool

def f(x):
return x * x if __name__ == '__main__':
with Pool(5) as p:
# map方法的作用是将f()方法并发地映射到列表中的每个元素
a = p.map(f, [1, 2, 3])
print(a)
# 异步执行map
b = p.map_async(f, [3, 5, 7, 11])
# b 是一个result对象,代表方法的执行结果
print(b)
# 为了拿到结果,使用join方法等待池中工作进程退出
p.close()
# 调用join方法前,需先执行close或terminate方法
p.join()
# 获取执行结果
print(b.get()) # 执行结果
# [1, 4, 9]
# <multiprocessing.pool.MapResult object at 0x10631b710>
# [9, 25, 49, 121]

map_async()apply_async()执行后会返回一个class multiprocessing.pool.AsyncResult对象,通过它的get()可以获取到执行结果,ready()可以判断AsyncResult的结果是否准备好。

进程间数据的传输

multiprocessing模块提供了两种方式用于进程间的数据共享:队列(Queue)和管道(Pipe)

Queue是线程安全,也是进程安全的。使用Queue可以实现进程间的数据共享,例如下面的demo中子进程put一个对象,在主进程中就能get到这个对象。

任何可以序列化的对象都可以通过Queue来传输。

from multiprocessing import Process, Queue

def f(q):
q.put([42, None, 'hello']) if __name__ == '__main__':
# 使用Queue进行数据通信
q = Queue()
p = Process(target=f, args=(q,))
p.start()
# 主进程取得子进程中的数据
print(q.get()) # prints "[42, None, 'hello']"
p.join() # 执行结果
# [42, None, 'hello']

Pipe()返回一对通过管道连接的Connection对象。这两个对象可以理解为管道的两端,它们通过send()recv()发送和接收数据。

from multiprocessing import Process, Pipe

def write(conn):
# 子进程中发送一个对象
conn.send([42, None, 'hello'])
conn.close() def read(conn):
# 在读的进程中通过recv接收对象
data = conn.recv()
print(data) if __name__ == '__main__':
# Pipe()方法返回一对连接对象
w_conn, r_conn = Pipe() wp = Process(target=write, args=(w_conn,))
rp = Process(target=read, args=(r_conn,)) wp.start()
rp.start() # 执行结果
# [42, None, 'hello']

需要注意的是,两个进程不能同时对一个连接对象进行sendrecv操作。

同步

我们知道线程间的同步是通过锁机制来实现的,进程也一样。

from multiprocessing import Process, Lock
import time def print_with_lock(l, i):
l.acquire()
try:
time.sleep(1)
print('hello world', i)
finally:
l.release() def print_without_lock(i):
time.sleep(1)
print('hello world', i) if __name__ == '__main__':
lock = Lock() # 先执行有锁的
for num in range(5):
Process(target=print_with_lock, args=(lock, num)).start()
# 再执行无锁的
# for num in range(5):
# Process(target=print_without_lock, args=(num,)).start()

有锁的代码将每秒依次打印

hello world 0
hello world 1
hello world 2
hello world 3
hello world 4

如果执行无锁的代码,则在我的电脑上执行结果是这样的

hello worldhello world  0
1
hello world 2
hello world 3
hello world 4

除了Lock,还包括RLockConditionSemaphoreEvent等进程间的同步原语。其用法也与线程间的同步原语很类似。API使用可以参考文末中引用的文档链接。

在工程中实现进程间的数据共享应当优先使用队列或管道。

0x02 总结

本文对multiprocessing模块中常见的API作了简单的介绍。讲述了ProcessPool的常见用法,同时介绍了进程间的数据方式:队列和管道。最后简单了解了进程间的同步原语。

通过与上篇的对比学习,本文的内容应该是更加容易掌握的。

0x03 引用

快速了解Python并发编程的工程实现(下)的更多相关文章

  1. 快速了解Python并发编程的工程实现(上)

    关于我 一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android.Python.Java和Go,这个也是我们团队的主要技术栈. Github:https:/ ...

  2. Python并发编程二(多线程、协程、IO模型)

    1.python并发编程之多线程(理论) 1.1线程概念 在传统操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程 线程顾名思义,就是一条流水线工作的过程(流水线的工作需要电源,电源就相当于 ...

  3. Python并发编程一(多进程)

    1.背景知识(进程.多道技术) 顾名思义,进程即正在执行的一个过程.进程是对正在运行程序的一个抽象. 进程的概念起源于操作系统,是操作系统最核心的概念,也是操作系统提供的最古老也是最重要的抽象概念之一 ...

  4. python并发编程之进程、线程、协程的调度原理(六)

    进程.线程和协程的调度和运行原理总结. 系列文章 python并发编程之threading线程(一) python并发编程之multiprocessing进程(二) python并发编程之asynci ...

  5. python并发编程&多线程(一)

    本篇理论居多,实际操作见:  python并发编程&多线程(二) 一 什么是线程 在传统操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程 线程顾名思义,就是一条流水线工作的过程,一 ...

  6. python并发编程&多进程(一)

    本篇理论居多,实际操作见:  python并发编程&多进程(二) 一 什么是进程 进程:正在进行的一个过程或者说一个任务.而负责执行任务则是cpu. 举例(单核+多道,实现多个进程的并发执行) ...

  7. Python并发编程内容回顾

    Python并发编程内容回顾 并发编程小结 目录 • 一.到底什么是线程?什么是进程? • 二.Python多线程情况下: • 三.Python多进程的情况下: • 四.为什么有这把GIL锁? • 五 ...

  8. Python并发编程-并发解决方案概述

    Python并发编程-并发解决方案概述 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.并发和并行区别 1>.并行(parallel) 同时做某些事,可以互不干扰的同一个时 ...

  9. python并发编程之线程/协程

    python并发编程之线程/协程 part 4: 异步阻塞例子与生产者消费者模型 同步阻塞 调用函数必须等待结果\cpu没工作input sleep recv accept connect get 同 ...

随机推荐

  1. 个人永久性免费-Excel催化剂功能第77波-专业图表制作辅助之批量维护序列点颜色及数据标签

    2018年最后一天工作日完成第77波,7是代表完美,2个7,双重的完美,Excel催化剂的2018年从始至终共77波都充满着完美接近极致的功能体验.感谢各位一路相随,陪伴成长.最后一波,再次让数据分析 ...

  2. 一个测试文件与源文件位于不同模块时Jacoco覆盖率配置的例子

    问题描述: 我们有个多模块项目,由于种种原因(更常见的可能是需要集成测试)测试文件和源文件不在一个模块,Jacoco的覆盖率无法正确显示,查询了一些资料,发现中文的例子比较少,就把我自己的Demo贴一 ...

  3. c语言进阶7-结构体

    一.  结构体: 在程序设计基础当中我们学习了变量,变量可以节省使用空间相对于常量而言,大家来看下表: 学号 姓名 职位 性别 数学 英语 语文 总成绩 1 刘琳 班委 女 50 61 56 167 ...

  4. 浏览器如何加载和解析CSS——CSS样式来源与层叠规则

    关于CSS样式首先得理解浏览器如何加载它们,最终的页面样式是如何呈现的? CSS层叠样式表的关键在于"层叠",会根据选择符的使用而将样式相互叠加或者覆盖. CSS样式表之所有有&q ...

  5. thinkphp 插件

    1.切换到项目根目录,使用composer require 5ini99/think-addons:dev-master命令安装thinkphp插件 如果是root用户或是管理员执行的话会有提示 等一 ...

  6. let和const解构赋值

    1.let和const:最基础也很容易理解的,let是 声明一个变量,const是声明一个常量. 具体细节看如下实例代码 { let a=; console.log(a) } // console.l ...

  7. 入门webpack,看这篇就够了

    什么是webpack? 官网给出的概念是:本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler).当 webpack 处理应用程序时,它会递 ...

  8. poj 1068 模拟

    题目链接 大概题意就是告诉你有个n个小括号,每一个")"左边有多少个"("都告诉你了,然后让你求出每一对括号之间有多少对括号(包含自己本身). 思路: 我先计算 ...

  9. Confluence未授权模板注入/代码执行(CVE-2019-3396)

    --- title: Confluence未授权模板注入/代码执行(CVE-2019-3396) tags: [poc,cve] num :g7y12 --- # 简介 --- Confluence是 ...

  10. django数据库事务

    数据库原子操作 举个例子: 一个消费者在一个商户里刷信用卡消费,交易正常时,银行在消费者的账户里减去相应的款项,在商户的帐户加上相应的款项.但是如果银行从消费者的账户里扣完钱之后,还未在商户的帐户里加 ...