进程,协程
一:进程:进程是操作系统结构的基础;是一个正在执行的程序;计算机中正在运行的程序实例;可以分配给处理器并由处理器执行的一个实体;由单一顺序的执行显示,一个当前状态和一组相关的系统资源所描述的活动单元
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 Process import time def 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 2016
hello alex Wed Oct 12 16:50:18 2016
end
2.类式调用
from multiprocessing import Process import time class 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 2016
hello MyProcess-3 Wed Oct 12 16:59:27 2016
end
3.进程关系(父进程与子进程之间的关系)
from multiprocessing import Process import os import time def 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: 12888
process id: 13432
bob
module name: __mp_main__
parent process: 13432
process 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, Queue def 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, Manager def 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, Pool import time import os def Foo(i): time.sleep(2) print('sub %s'%os.getpid()) return i + 100 def 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") yield f() # 这个时候是什么都没有,为什么,这里的yield是一个生成器 print(f()) #<generator object f at 0x00000000006AC048>,那我们怎样去打印出上面的结果呢,看下面加的一些
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()) #<generator object f at 0x00000000006AC048> gen=f() # ret=next(gen) # print(ret) next(gen)#gen.send(None)# x=gen.send(10) print(x)#67
再来看一个例子
import time import queue def 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 greenlet def 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>
12
56
34
78
3.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 2016
explicit context switch to foo again Fri Oct 14 17:18:39 2016
implicit 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) #