进程和协程详解(自动化运维-13)
进程:
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
Python中提供了multiprocessing这个包实现多进程。multiprocessing支持子进程、进程间的同步与通信,提供了Process、Queue、Pipe、Lock等组件。这是一个跨平台的模块,因为windows不支持fork()调用,fork() 是unix系统特有的。多进程的实现是,复制父进程的内容到子进程,所以父进程中的所有数据在生成子进程的时候会执行一次。
multiprocessing 启用多进程示例
from multiprocessing import Process # 因为 multiprocessing 是包,所以只导入需要的子模块
import time,os
def name(name):
    time.sleep(5)
    print("My name is %s" % name)
if __name__ == "__main__": # windows 下执行时,必须写上,否则不会生成进程
    p_res = []
    for i in range(5):
        p = Process(target=name,args=("bushaoxun"+str(i),))
        p.start()
        p_res.append(p)
    for i in  p_res:
        i.join()
print("I love you",os.getpid())# os.getpid() 获取当前进程的 pid
# 执行结果
I love you 5356
I love you 3044
I love you 3068
I love you 11692
I love you 8928   # 因为复制一个父进程的内容,所以执行了五次 print.
My name is bushaoxun0
My name is bushaoxun3
My name is bushaoxun2
My name is bushaoxun1
My name is bushaoxun4
I love you 1244
进程间通讯
1. Queue
from multiprocessing import Process,Queue
def name(q):
    q.put("bushaoxun")
if __name__ == "__main__":
    q = Queue()
    print("进程执行前",q.qsize())
    p = Process(target=name,args=(q,)) # 把对象q传入子进程,实际上是克隆一份,然后通过序列化再返回给主进程。
    p.start()
    p.join()
    print(q.get()) #主进程获得了子进程更改的数据
# 执行结果
进程执行前 0
bushaoxun
2.Pipes
multiprocessing.Pipe()即管道模式,调用Pipe()返回管道的两端的Connection。
from multiprocessing import Process,Pipe
def f(conn):
    conn.send("My name is bushaoxun")  # 发送一次,接收一次
    conn.send("My name is shaoxun")
    conn.close()
if __name__ == "__main__":
    parent_conn,child_conn = Pipe() # 管道返回两个连接,一个接收数据,一个发送数据
    p = Process(target=f,args=(child_conn,))
    p.start()
    print(parent_conn.recv())
    print(parent_conn.recv())
    p.join()
# 执行结果
My name is bushaoxun
My name is shaoxun
Maneger
Python实现多进程间通信的方式有很多种,例如队列,管道等。但是这些方式只适用于多个进程都是源于同一个父进程的情况。如果多个进程不是源于同一个父进程只能用共享内存,信号量等方式,但是这些方式对于复杂的数据结构,例如Queue,dict,list等,使用起来比较麻烦,不够灵活。Manager是一种较为高级的多进程通信方式,它能支持Python支持的的任何数据构。
它的原理是:先启动一个ManagerServer进程,这个进程是阻塞的,它监听一个socket,然后其他进程(ManagerClient)通过socket来连接到ManagerServer,实现通信。
from multiprocessing import Process,Manager
import os,random
def f(dic,list):
    dic["pid-"+str(os.getpid())] = os.getpid()
    list.append(random.randint(1,5))
    print(dic)
    print(list)
if  __name__ == "__main__":
    #with Manager() as manager:
    manager = Manager() # 返回一个进程,监听 socket.
    dic = manager.dict()
    print(type(dic))
    list = manager.list()
    print(type(list))
    p_list = []
    for i in  range(10):
        p = Process(target=f,args=(dic,list))
        p.start()
        p_list.append(p)
    for i in p_list:
        i.join()
    print(dic)
    print(list)
# 执行结果
<class 'multiprocessing.managers.DictProxy'>
<class 'multiprocessing.managers.ListProxy'>
{'pid-1992': 1992}
[5]
{'pid-1992': 1992, 'pid-16120': 16120}
[5, 4]
{'pid-1992': 1992, 'pid-16120': 16120, 'pid-10844': 10844}
[5, 4, 5]
{'pid-1992': 1992, 'pid-16120': 16120, 'pid-10844': 10844, 'pid-18112': 18112}
[5, 4, 5, 4]
{'pid-1992': 1992, 'pid-16120': 16120, 'pid-10844': 10844, 'pid-18112': 18112, 'pid-1300': 1300}
[5, 4, 5, 4, 5]
{'pid-1992': 1992, 'pid-16120': 16120, 'pid-10844': 10844, 'pid-18112': 18112, 'pid-1300': 1300, 'pid-15980': 15980}
[5, 4, 5, 4, 5, 3]
{'pid-1992': 1992, 'pid-16120': 16120, 'pid-10844': 10844, 'pid-18112': 18112, 'pid-1300': 1300, 'pid-15980': 15980, 'pid-5208': 5208}
[5, 4, 5, 4, 5, 3, 4]
{'pid-1992': 1992, 'pid-16120': 16120, 'pid-10844': 10844, 'pid-18112': 18112, 'pid-1300': 1300, 'pid-15980': 15980, 'pid-5208': 5208, 'pid-13500': 13500}
[5, 4, 5, 4, 5, 3, 4, 1]
{'pid-1992': 1992, 'pid-16120': 16120, 'pid-10844': 10844, 'pid-18112': 18112, 'pid-1300': 1300, 'pid-15980': 15980, 'pid-5208': 5208, 'pid-13500': 13500, 'pid-18160': 18160}
[5, 4, 5, 4, 5, 3, 4, 1, 5]
{'pid-1992': 1992, 'pid-16120': 16120, 'pid-10844': 10844, 'pid-18112': 18112, 'pid-1300': 1300, 'pid-15980': 15980, 'pid-5208': 5208, 'pid-13500': 13500, 'pid-18160': 18160, 'pid-6772': 6772}
[5, 4, 5, 4, 5, 3, 4, 1, 5, 4]
<class 'multiprocessing.managers.DictProxy'>
[5, 4, 5, 4, 5, 3, 4, 1, 5, 4]
进程锁
进程是独享内存的,为什么还要给进程加锁呢,比如,所以进程同时打印输出,进程不会乱,但是屏幕输出的时候可能会乱,他们会争夺打印。所以这种情况下是需要加锁的
from multiprocessing import Process,Lock
def f(lock,i):
    lock.acquire()
    print("my name is bushaoxun-"+str(i))
    lock.release()
if __name__ == "__main__":
    lock = Lock()
    for num in range(10):
        t = Process(target=f,args=(lock,num))
        t.start()
# 执行结果
my name is bushaoxun-0
my name is bushaoxun-3
my name is bushaoxun-1
my name is bushaoxun-2
my name is bushaoxun-4
my name is bushaoxun-5
my name is bushaoxun-6
my name is bushaoxun-7
my name is bushaoxun-8
my name is bushaoxun-9
进程池
进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止。
进程池中有两个方法:
- apply 此方法是阻塞的,意思就是当前子进程执行完毕后,再执行下一个进程
- apply_async 此方法是异步非阻塞的,意思就是不用等待当前进程执行完毕,随时根据系统调度来进行进程切换。
from multiprocessing import Process,Pool
import time
def name(i):
    time.sleep(2)
    print("I am bushaoxun-"+str(i))
    return i+100
def bar(arg):
    print("args:",arg)
if __name__ == "__main__": # 必须写这个,否则会报错
    pool = Pool(5)
    for i in range(10):
    # callback ,进程执行完毕的回调函数,也就是进程结束后做些什么事
        pool.apply_async(func=name,args=(i,),callback=bar) # 异步非阻塞,并发执行
    print("done")
    pool.close()
    pool.join() # 进程池中的进程执行完毕后再关闭,如果注释,程序直接关闭
# 执行结果,等待两秒后,顺序打印,是并发执行的效果
done
I am bushaoxun-0
args: 100
I am bushaoxun-1
args: 101
I am bushaoxun-2
args: 102
I am bushaoxun-3
args: 103
I am bushaoxun-4
args: 104
I am bushaoxun-5
args: 105
I am bushaoxun-6
args: 106
I am bushaoxun-7
args: 107
I am bushaoxun-8
args: 108
I am bushaoxun-9
args: 109
协程
协程,又称微线程,纤程。英文名Coroutine。协程是一种用户态的轻量级线程。
协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:
协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。
协程的好处:
- 无需线程上下文切换的开销
- 无需原子操作锁定及同步的开销
- 方便切换控制流,简化编程模型
- 高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理
缺点:
- 无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。
- 进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序
协程的标准定义:
- 必须在只有一个单线程里实现并发
- 修改共享数据不需加锁
- 用户程序里自己保存多个控制流的上下文栈
- 一个协程遇到IO操作自动切换到其它协程
Greenlet
greenlet是一个用C实现的协程模块,相比与python自带的yield,它可以使你在任意函数之间随意切换,而不需把这个函数先声明为generator
from greenlet import greenlet
if __name__ == "__main__":  # Windows 下执行必须加这个,否则报错
    def test():
        print(1)
        gr2.switch()
        print(2)
        gr2.switch()
    def test1():
        print(3)
        gr1.switch()
        print(4)
    gr1 = greenlet(test)
    gr2 = greenlet(test1)
    gr1.switch()
# 执行结果
1
3
2
4
Gevnet
Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。
gevent 默认遇到 IO 阻塞不自动切换,切换方式有一下两种:
- gevent.sleep(- 0.5- )指定切换
- monkey.patch_all()自动切换
爬取网站 URL ,协程非阻塞 IO 并发实例。 # 我们的程序是单线程中的协程,遇到 IO ,交给操作系统处理。自动切换 IO.
from gevent import monkey
import gevent,time
from urllib.request import urlopen
monkey.patch_all() # 遇到 IO 自动检测切换
def f(url):
   print("GET: %s" % url)
   resp = urlopen(url)
   data = resp.read()
   time.sleep(1)
   print("%d bytes received from %s." %(len(data),url))
gevent.joinall([
    gevent.spawn(f,"https://www.baidu.com"),
    gevent.spawn(f,"https://www.bjfles.com"),
    gevent.spawn(f,"https://www.sina.com.cn"),
    gevent.spawn(f,"https://m.autohome.com.cn/"),
])
# 执行结果
GET: https://www.baidu.com
GET: https://www.bjfles.com
GET: https://www.sina.com.cn
GET: https://m.autohome.com.cn/
227 bytes received from https://www.baidu.com.
44371 bytes received from https://m.autohome.com.cn/.
587275 bytes received from https://www.sina.com.cn.
2947240 bytes received from https://www.bjfles.com.
 
                    
                
 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号