一:进程:进程是操作系统结构的基础;是一个正在执行的;计算机中正在运行的程序实例;可以分配给并由处理器执行的一个实体;由单一顺序的执行显示,一个当前状态和一组相关的系统资源所的活动单元
1.多进程
由于GIL的存在,python中的多线程其实并不是真正的多线程,如果想要充分地使用多核CPU的资源,在python中大部分情况需要使用多进程。Python提供了非常好用的多进程包multiprocessing,只需要定义一个函数,Python会完成其他所有事情。借助这个包,可以轻松完成从单进程到并发执行的转换。multiprocessing支持子进程、通信和共享数据、执行不同形式的同步,提供了Process、Queue、Pipe、Lock等组件。 multiprocessing包是Python中的多进程管理包。与threading.Thread类似,它可以利用multiprocessing.Process对象来创建一个进程。该进程可以运行在Python程序内部编写的函数。该Process对象与Thread对象的用法相同,也有start(), run(), join()的方法。此外multiprocessing包中也有Lock/Event/Semaphore/Condition类 (这些对象可以像多线程那样,通过参数传递给各个进程),用以同步进程,其用法与threading包中的同名类一致。所以,multiprocessing的很大一部份与threading使用同一套API,只不过换到了多进程的情境。但在使用这些共享API的时候,我们要注意以下几点:在UNIX平台上,当某个进程终结之后,该进程需要被其父进程调用wait,否则进程成为僵尸进程(Zombie)。所以,有必要对每个Process对象调用join()方法 (实际上等同于wait)。对于多线程来说,由于只有一个进程,所以不存在此必要性。multiprocessing提供了threading包中没有的IPC(比如Pipe和Queue),效率上更高。应优先考虑Pipe和Queue,避免使用Lock/Event/Semaphore/Condition等同步方式 (因为它们占据的不是用户进程的资源)。多进程应该避免共享资源。在多线程中,我们可以比较容易地共享资源,比如使用全局变量或者传递参数。在多进程情况下,由于每个进程有自己独立的内存空间,以上方法并不合适。此时我们可以通过共享内存和Manager的方法来共享资源。但这样做提高了程序的复杂度,并因为同步的需要而降低了程序的效率。Process.PID中保存有PID,如果进程还没有start(),则PID为None。window系统下,需要注意的是要想启动一个子进程,必须加上那句if __name__ == "main",进程相关的要写在这句下面。
来直接看一下代码
from multiprocessing import Processimport timedef f(name): time.sleep(1) print("hello",name,time.ctime())if __name__ == "__main__": p_list = [] for i in range(3): p = Process(target = f,args = ("alex",)) p_list.append(p) p.start() for i in p_list: p.join() #注意这个地方得到join,等到上面的所有执行完以后再执行最后的end print("end") 结果为:
hello alex Wed Oct 12 16:50:18 2016
hello alex Wed Oct 12 16:50:18 2016hello alex Wed Oct 12 16:50:18 2016end2.类式调用
from multiprocessing import Processimport timeclass MyProcess(Process): def __init__(self,): super(MyProcess,self).__init__() #self.name = name def run(self): time.sleep(1) print("hello",self.name,time.ctime()) #此时的self.name 是进程对象下的一个属性,是有名字的,if __name__ == "__main__": p_list =[] for i in range(3): p = MyProcess() p.start() p_list.append(p) for p in p_list: p.join() print("end") 结果为:
hello MyProcess-1 Wed Oct 12 16:59:27 2016
hello MyProcess-2 Wed Oct 12 16:59:27 2016hello MyProcess-3 Wed Oct 12 16:59:27 2016end3.进程关系(父进程与子进程之间的关系)
from multiprocessing import Processimport osimport timedef info(title): print(title) print("module name:",__name__) print("parent process:",os.getppid()) print("process id:",os.getpid())def f(name): info("\033[3;1mfunction f\033[0m") print("hello",name)if __name__ == "__main__": info("\033[32;1mmain process line\033[0m") time.sleep(2) p = Process(target = info,args=("bob",)) p.start() p.join() 结果为:
main process line
module name: __main__parent process: 12888process id: 13432bobmodule name: __mp_main__parent process: 13432process id: 13036二:Process类
构造方法:Process([group [, target [, name [, args [, kwargs]]]]]) group: 线程组,目前还没有实现,库引用中提示必须是None; target: 要执行的方法; name: 进程名; args/kwargs: 要传入方法的参数。实例方法: is_alive():返回进程是否在运行。 join([timeout]):阻塞当前上下文环境的进程程,直到调用此方法的进程终止或到达指定的timeout(可选参数)。 start():进程准备就绪,等待CPU调度 run():strat()调用run方法,如果实例进程时未制定传入target,这star执行t默认run()方法。 terminate():不管任务是否完成,立即停止工作进程属性: authkey daemon:和线程的setDeamon功能一样 exitcode(进程在运行时为None、如果为–N,表示被信号N结束) name:进程名字。 pid:进程号。
三:进程间通讯:这里都是通过代码来看的
3.1首先来看一个队列,进程队列queue
from multiprocessing import Process, Queuedef f(q,n): q.put([42, n, 'hello'])if __name__ == '__main__': q = Queue() p_list=[] for i in range(3): p = Process(target=f, args=(q,i)) p_list.append(p) p.start() print(q.get()) print(q.get()) print(q.get()) for i in p_list: i.join()
3.2 pipe
# import os## from multiprocessing import Process, Pipe## def f(conn):# conn.send('约吗')# print(conn.recv(),'in the %s'%os.getpid())# conn.close()## if __name__ == '__main__':# parent_conn, child_conn = Pipe()# p = Process(target=f, args=(child_conn,))# p2 = Process(target=f, args=(child_conn,))# p.start()# p2.start()# print(parent_conn.recv()) # prints "[42, None, 'hello']"# print(parent_conn.recv()) # prints "[42, None, 'hello']"# parent_conn.send('hello')# parent_conn.send('hello2')## p.join()
4. manager 来实现一个数据共享
from multiprocessing import Process, Managerdef f(d,l,n): d[n] = '1' d['2'] = 2 d[0.25] = None l.append(n) #print(l) print('sub',id(d))if __name__ == '__main__': with Manager() as manager:# with open() as f== f=open() manager=Manager() d = manager.dict() l = manager.list(range(5)) p_list = [] print('main',id(d)) for i in range(10): p = Process(target=f, args=(d,l,i)) p.start() p_list.append(p) for res in p_list: res.join() print(d) print(l)
四:进程池
进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止。
进程池中有两个方法:
- apply
- apply_async
from multiprocessing import Process, Poolimport timeimport osdef Foo(i): time.sleep(2) print('sub %s'%os.getpid()) return i + 100def Bar(arg): print('Bar:',os.getpid()) print('-->exec done:', arg)if __name__=='__main__': pool = Pool() print('main:',os.getpid()) for i in range(10): pool.apply_async(func=Foo, args=(i,),callback=Bar) #pool.apply(func=Foo, args=(i,)) print('end') pool.close() pool.join()
第二部分: 协程
协程协程,又称微线程,纤程。英文名Coroutine。一句话说明什么是线程:协程是一种用户态的轻量级线程。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。协程的好处:无需线程上下文切换的开销无需原子操作锁定及同步的开销方便切换控制流,简化编程模型高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。缺点:无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序
我们来看例子慢慢认识协程
1. yield支持下的协程 yield有一个并发的效果,在这里其实实现的就是一个协程
首先我们先来简单的回顾一下yield,比如说
def f(): print("ok")f() # 这种情况下回正常执行函数打印出结果为OK,看下面def f(): print("OK") yieldf() # 这个时候是什么都没有,为什么,这里的yield是一个生成器print(f()) #,那我们怎样去打印出上面的结果呢,看下面加的一些
gen = f() next(gen)
next(gen) #这样我们就可以取到值 注意: gen.__next__()和next(gen)是等价的
如果我们在yield下面加上这样
print('ok1') yield 5 print("OK2") 这样的话OK2也是不会打印的,因为此时yield已经折回了,当然要想打印出OK2,我们再来一个next就行,还需要一个yield yield 7 最后面next(gen)这样就可以实现 yield 后面是可以跟参数的,那么我们如何取到yield后面的参数值呢,因为返回的其实就是next(gen),所以呢,看下面这样就可以取到返回值 ret = next(gen) print(ret) 此时取到的就是返回值
我们除了next进入生成器对象里面我们还可以通过send进入,看下面例子 count = yield 5 print(count) x = gen.send(10) print(x) 但是如果我们开始直接就send一个值是不行的,必须传入一个空值才行,就是gen.send(None) 我这里写的有一点乱,可以看下面的代码在一步步看我的就而已理解了
def f(): print('ok1') count=yield 5 print(count) print('ok2') yield 67#print(f()) #gen=f()# ret=next(gen)# print(ret)next(gen)#gen.send(None)#x=gen.send(10)print(x)#67
再来看一个例子
import timeimport queuedef consumer(name): print("--->starting ...") while True: new_baozi = yield print("[%s] is eating baozi %s" % (name, new_baozi)) # time.sleep(1)def producer(): next(con) next(con2) n = 0 while n < 5: n += 1 print("\033[32;1m[producer]\033[0m is making baozi %s" % n) con.send(n) con2.send(n)if __name__ == '__main__': con = consumer("c1") # 创建一个生成器对象con con2 = consumer("c2") # 创建一个生成器对象con2 p = producer() # 执行producer函数,p就是函数返回值
2.Greenlet 下的协程
from greenlet import greenletdef test1(): print(12) gr2.switch() print(34) gr2.switch()def test2(): print (56) gr1.switch() print (78)gr1 = greenlet(test1)gr2 = greenlet(test2) #这里也是生成了一个对象print(gr1)print(gr2) 结果为:
<greenlet.greenlet object at 0x000000CD25A1FCC0>
<greenlet.greenlet object at 0x000000CD25A1FDF0>125634783.gevent 下 支持 的 协程
Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。
首先我们要装一个gevent,在file下找到setting下的这个选项就可以在里面添加各种你所需要的配置
来看一个例子
# import gevent# import time## def foo():# print('Running in foo',time.ctime())# gevent.sleep(1) #这里有同学可能会问为什么不用time,如果用time的话这就是一个串行的过程,就不会切换了,# print('Explicit context switch to foo again',time.ctime())### def bar():# print('Explicit context to bar',time.ctime())# gevent.sleep(2)# print('Implicit context switch back to bar',time.ctime())## gevent.joinall([# gevent.spawn(foo),# gevent.spawn(bar),# ]) 结果为:
runing in foo Fri Oct 14 17:18:38 2016
explicit context to bar Fri Oct 14 17:18:38 2016explicit context switch to foo again Fri Oct 14 17:18:39 2016implicit context switch back to bar Fri Oct 14 17:18:40 2016
再来看一个爬虫的例子,比如说校花网
#爬虫# from gevent import monkey# monkey.patch_all() #最大程度的利用IO阻塞# import gevent# from urllib.request import urlopen# import time# def f(url):# print("GET: %s" % url)# resp = urlopen(url)# data = resp.read()# with open("xiaohuawang.html","wb")as f:# f.write(data)# print("%d bytes received from %s." % (len(data),url))# #l = ["http://www.python.org/","http://www.yahoo.com/","http://github.com/"]# start = time.time()# for url in l:# f(url) #是等价于下面的,# gevent.joinall([# gevent.spawn(f,"http://www.xiaohuar.com/"),# # gevent.spawn(f,"http://www.yahoo.com/"),# # gevent.spawn(f,"http://github.com/"),# ])# print(time.time() - start)#