进程,协程

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了进程,协程相关的知识,希望对你有一定的参考价值。

一:进程:进程是操作系统结构的基础;是一个正在执行的程序;计算机中正在运行的程序实例;可以分配给处理器并由处理器执行的一个实体;由单一顺序的执行显示,一个当前状态和一组相关的系统资源所描述的活动单元

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)
#

 

以上是关于进程,协程的主要内容,如果未能解决你的问题,请参考以下文章

python 多进程,多线程,协程

协程

进程线程协程

进程线程和协程的区别

进程线程协程的区别

协程python