一、多进程
1、子进程(subprocess包)
在python中,通过subprocess包,fork一个子进程,并运行外部程序。
import subprocess
child1 = subprocess.Popen(["ls","-l"], stdout=subprocess.PIPE)
child2 = subprocess.Popen(["wc"], stdin=child1.stdout,stdout=subprocess.PIPE)
out = child2.communicate()
print(out)
subprocess参考
从subprocess运行的子进程中实时获取输出
2、多进程(multiprocessing包)
它可以利用multiprocessing.Process对象来创建一个进程。
进程池 (Process Pool)可以创建多个进程。
apply_async(func,args) 从进程池中取出一个进程执行func,args为func的参数。它将返回一个AsyncResult的对象,你可以对该对象调用get()方法以获得结果。
close() 进程池不再创建新的进程
join()方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步。
join() wait进程池中的全部进程。必须对Pool先调用close()方法才能join。
#! /usr/bin/env python
# -*- coding:utf-8 -*-
# __author__ == "111"
# "我的电脑有4个cpu"
from multiprocessing import Pool
import os, time
def long_time_task(name):
print 'Run task %s (%s)...' % (name, os.getpid())
start = time.time()
time.sleep(3)
end = time.time()
print 'Task %s runs %0.2f seconds.' % (name, (end - start))
if __name__=='__main__':
print 'Parent process %s.' % os.getpid()
p = Pool()
for i in range(4):
p.apply_async(long_time_task, args=(i,))
print 'Waiting for all subprocesses done...'
p.close()
p.join()
print 'All subprocesses done.'
多进程共享资源
通过共享内存和Manager对象:用一个进程作为服务器,建立Manager来真正存放资源。
其它的进程可以通过参数传递或者根据地址来访问Manager,建立连接后,操作服务器上的资源。
#! /usr/bin/env python
# -*- coding:utf-8 -*-
# __author__ == "111"
from multiprocessing import Queue,Pool
import multiprocessing,time,random
def write(q):
for value in ['A','B','C','D']:
print "Put %s to Queue!" % value
q.put(value)
time.sleep(random.random())
def read(q,lock):
while True:
lock.acquire()
if not q.empty():
value=q.get(True)
print "Get %s from Queue" % value
time.sleep(random.random())
else:
break
lock.release()
if __name__ == "__main__":
manager=multiprocessing.Manager()
q=manager.Queue()
p=Pool()
lock=manager.Lock()
pw=p.apply_async(write,args=(q,))
pr=p.apply_async(read,args=(q,lock))
p.close()
p.join()
print ("所有数据都写入并且读完")
二、多线程
threading包
import time, threading
# 新线程执行的代码:
def loop():
print('thread %s is running...' % threading.current_thread().name)
n = 0
while n < 5:
n = n + 1
print('thread %s >>> %s' % (threading.current_thread().name, n))
time.sleep(1)
print('thread %s ended.' % threading.current_thread().name)
print('thread %s is running...' % threading.current_thread().name)
t = threading.Thread(target=loop, name='LoopThread')
t.start()
t.join()
print('thread %s ended.' % threading.current_thread().name)
python的多线程在同一时刻只会有一条线程跑在CPU里面,其他线程都在睡觉。这个就是因为传说中的GIL(全局解释锁)的存在。任何Python线程执行前,必须先获得GIL锁.
那python多线程还有用处吗?当然!
如果是一个计算为主的程序(专业一点称为CPU密集型程序),这一点确实是比较吃亏的,每个线程运行一遍,就相当于单线程在跑,甚至比单线程还要慢——CPU切换线程的上下文也是要有开销的。
辣鸡
如果是一个磁盘或网络为主的程序(IO密集型)就不同了。一个线程处在IO等待的时候,另一个线程还可以在CPU里面跑,有时候CPU闲着没事干,所有的线程都在等着IO,这时候他们就是同时的了,而单线程的话此时还是在一个一个等待的。我们都知道IO的速度比起CPU来是慢到令人发指的,python的多线程就在这时候发挥作用了。比方说多线程网络传输,多线程往不同的目录写文件,等等。
线程同步
多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了.
一个线程使用自己的局部变量比使用全局变量好,因为局部变量只有线程自己能看见,不会影响其他线程,而全局变量的修改必须加锁。
import time, threading
# 假定这是你的银行存款:
balance = 0
def change_it(n):
# 先存后取,结果应该为0:
global balance
balance = balance + n
balance = balance - n
def run_thread(n):
for i in range(100000):
change_it(n)
t1 = threading.Thread(target=run_thread, args=(5,))
t2 = threading.Thread(target=run_thread, args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
print(balance)
解决:加锁
balance = 0
lock = threading.Lock()
def run_thread(n):
for i in range(100000):
# 先要获取锁:
lock.acquire()
try:
# 放心地改吧:
change_it(n)
finally:
# 改完了一定要释放锁:
lock.release()
三、协程
协程,又称微线程,纤程。英文名Coroutine。一句话说明什么是线程:协程是一种用户态的轻量级线程。
线程是系统级别的,它们是由操作系统调度;协程是程序级别的,由程序员根据需要自己调度。我们把一个线程中的一个个函数叫做子程序,那么子程序在执行过程中可以中断去执行别的子程序;别的子程序也可以中断回来继续执行之前的子程序,这就是协程。也就是说同一线程下的一段代码<1>执行着执行着就可以中断,然后跳去执行另一段代码,当再次回来执行代码块<1>的时候,接着从之前中断的地方开始执行。
协程的优点:
(1)无需线程上下文切换的开销,协程避免了无意义的调度,由此可以提高性能(但也因此,程序员必须自己承担调度的责任,同时,协程也失去了标准线程使用多CPU的能力)
(2)无需原子操作锁定及同步的开销
(3)方便切换控制流,简化编程模型
(4)高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。
协程的缺点:
(1)无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。
(2)进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序
参考:
https://www.cnblogs.com/zingp/p/5911537.html
https://blog.csdn.net/andybegin/article/details/77884645
四、异步
无论是线程还是进程,使用的都是同步进制,当发生阻塞时,性能会大幅度降低,无法充分利用CPU潜力,浪费硬件投资,更重要造成软件模块的铁板化,紧耦合,无法切割,不利于日后扩展和变化。
不管是进程还是线程,每次阻塞、切换都需要陷入系统调用(system call),先让CPU跑操作系统的调度程序,然后再由调度程序决定该跑哪一个进程(线程)。多个线程之间在一些访问互斥的代码时还需要加上锁,
现下流行的异步server都是基于事件驱动的(如nginx)。
异步事件驱动模型中,把会导致阻塞的操作转化为一个异步操作,主线程负责发起这个异步操作,并处理这个异步操作的结果。由于所有阻塞的操作都转化为异步操作,理论上主线程的大部分时间都是在处理实际的计算任务,少了多线程的调度时间,所以这种模型的性能通常会比较好。