Python之路--并发编程之进程并发

一.进程相关概念

1.什么是进程

一段正在执行的程序的过程称为进程,例如:启动QQ,音乐等,都是开启了一个进程

执行进程的执行单元是CPU,实现进程的并发采用的是单核加上多道技术

2.进程和程序的区别

程序仅仅是一堆代码,进程是程序的执行过程

例如:小明从一个北京到另外一个上海,从北京来上海 的过程就是一个进程

3.并行与并发的概念

无论是并行还是并发,在用户看来都是'同时'运行的,不管是进程还是线程,都只是一个任务而已,真是干活的是cpu,cpu来做这些任务,而一个cpu同一时刻只能执行一个任务

并发:是一种伪并行,看起来是同时运行,但实际上是利用单核+多道技术实现

并行:要想并行运行必须要具备多个CPU,因为同一时刻CPU只能执行一个任务

单核下,可以利用多道技术,多个核,每个核也都可以利用多道技术(多道技术是针对单核而言的

         有四个核,六个任务,这样同一时间有四个任务被执行,假设分别被分配给了cpu1,cpu2,cpu3,cpu4,

         一旦任务1遇到I/O就被迫中断执行,此时任务5就拿到cpu1的时间片去执行,这就是单核下的多道技术

         而一旦任务1的I/O结束了,操作系统会重新调用它(需知进程的调度、分配给哪个cpu运行,由操作系统说了算),可能被分配给四个cpu中的任意一个去执行

所有现代计算机经常会在同一时间做很多件事,一个用户的PC(无论是单cpu还是多cpu),都可以同时运行多个任务(一个任务可以理解为一个进程)。

    启动一个进程来杀毒(360软件)

    启动一个进程来看电影(暴风影音)

    启动一个进程来聊天(腾讯QQ)

所有的这些进程都需被管理,于是一个支持多进程的多道程序系统是至关重要的

多道技术概念回顾:内存中同时存入多道(多个)程序,cpu从一个进程快速切换到另外一个,使每个进程各自运行几十或几百毫秒,这样,虽然在某一个瞬间,一个cpu只能执行一个任务,但在1秒内,cpu却可以运行多个进程,这就给人产生了并行的错觉,即伪并发,以此来区分多处理器操作系统的真正硬件并行(多个cpu共享同一个物理内存)

 

 4.同步,异步,非阻塞,阻塞(重点需要理解)

同步:

#所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不会返回。按照这个定义,
其实绝大多数函数都是同步调用。
但是一般而言,我们在说同步、异步的时候,特指那些需要其他部件协作或者需要一定时间完成的任务。
#举例: #1. multiprocessing.Pool下的apply #发起同步调用后,就在原地等着任务结束,
根本不考虑任务是在计算还是在io阻塞,总之就是一股脑地等任务结束
#2. concurrent.futures.ProcessPoolExecutor().submit(func,).result() #3. concurrent.futures.ThreadPoolExecutor().submit(func,).result()

异步:

#异步的概念和同步相对。当一个异步功能调用发出后,调用者不能立刻得到结果。
当该异步功能完成后,通过状态、通知或回调来通知调用者。如果异步功能用状态来通知,
那么调用者就需要每隔一定时间检查一次,效率就很低
(有些初学多线程编程的人,总喜欢用一个循环去检查某个变量的值,这其实是一 种很严重的错误)。
如果是使用通知的方式,效率则很高,因为异步功能几乎不需要做额外的操作。至于回调函数,其实和通知没太多区别。
#举例: #1. multiprocessing.Pool().apply_async() #发起异步调用后,并不会等待任务结束才返回,
相反,会立即获取一个临时结果(并不是最终的结果,可能是封装好的一个对象)。
#2. concurrent.futures.ProcessPoolExecutor(3).submit(func,) #3. concurrent.futures.ThreadPoolExecutor(3).submit(func,)

阻塞:

#阻塞调用是指调用结果返回之前,当前线程会被挂起(如遇到io操作)。
函数只有在得到结果之后才会将阻塞的线程激活。有人也许会把阻塞调用和同步调用等同起来,
实际上他是不同的。对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已。
#举例: #1. 同步调用:apply一个累计1亿次的任务,该调用会一直等待,
直到任务返回结果为止,但并未阻塞住(即便是被抢走cpu的执行权限,那也是处于就绪态);
#2. 阻塞调用:当socket工作在阻塞模式的时候,如果没有数据的情况下调用recv函数,
则当前线程就会被挂起,直到有数据为止。

非阻塞:

#非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前也会立刻返回,同时该函数不会阻塞当前线程。

知识小结:

#1. 同步与异步针对的是函数/任务的调用方式:同步就是当一个进程发起一个函数(任务)调用的时候,
一直等到函数(任务)完成,而进程继续处于激活状态。
而异步情况下是当一个进程发起一个函数(任务)调用的时候,不会等函数返回,
而是继续往下执行当,函数返回的时候通过状态、通知、事件等方式通知进程任务完成。
#2. 阻塞与非阻塞针对的是进程或线程:阻塞是当请求不能满足的时候就将进程挂起,而非阻塞则不会阻塞当前进程

5.进程的层次结构

在Unix中和window操作系统中进程的相同点与不同点:

(1)相同点:进程中只有一个父进程

(2)不同点:

1. 在UNIX中所有的进程,都是以init进程为根,组成树形结构。父子进程共同组成一个进程组,这样,当从键盘发出一个信号时,该信号被送给当前与键盘相关的进程组中的所有成员。

  2. 在windows中,没有进程层次的概念,所有的进程都是地位相同的,唯一类似于进程层次的暗示,是在创建进程时,父进程得到一个特别的令牌(称为句柄),该句柄可以用来控制子进程,但是父进程有权把该句柄传给其他子进程,这样就没有层次了。

6.进程的状态

进程中一个只有三个状态:运行态,阻塞态和就绪态

 

7.进程的并发

进程的并发是通过CPU的切换来实现的,CPU在切换任务之前要把这个任务的状态给保存下来,让下次切回来的时候可以继续执行,为此,操作系统维护一张表格,即进程表(process table),每个进程占用一个进程表项(这些表项也称为进程控制块)

该表存放了进程状态的重要信息:程序计数器、堆栈指针、内存分配状况、所有打开文件的状态、帐号和调度信息,以及其他在进程由运行态转为就绪态或阻塞态时,必须保存的信息,从而保证该进程在再次启动时,就像从未被中断过一样。

二.multiprocessing模块

1.multiprocessing模块介绍

python中的多线程无法利用多核优势,如果要充分地使用多核CPU的资源,在python中大部分的情况下要使用多进程,要开启多进程,python中提供了multiprocessing模块。

multipprocessing模块开启子进程,并在子进程中执行我们定制的任务,multiprocessing模块(开多进程)和threading模块(开多线程)功能相似,都提供相同的接口。

multiprocessing模块的功能众多:支持子进程、通信和共享数据、执行不同形式的同步,提供了Process、Queue、Pipe、Lock等组件。

    需要再次强调的一点是:与线程不同,进程没有任何共享状态,进程修改的数据,改动仅限于该进程内。

2.process类的介绍

(1)process是一个创建进程的类:

Process([group [, target [, name [, args [, kwargs]]]]]),由该类实例化得到的对象,表示一个子进程中的任务(尚未启动)

强调:
1. 需要使用关键字的方式来指定参数
2. args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号

(2)参数介绍

1 group参数未使用,值始终为None
2 
3 target表示调用对象,即子进程要执行的任务
4 
5 args表示调用对象的位置参数元组,args=(1,2,'egon',)
6 
7 kwargs表示调用对象的字典,kwargs={'name':'egon','age':18}
8 
9 name为子进程的名称

(3)方法介绍

 1 p.start():启动进程,并调用该子进程中的p.run()
2 p.run():进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法
3 p.terminate():强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放,进而导致死锁
4
p.is_alive():如果p仍然运行,返回True
5 p.join([timeout]):主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态)。timeout是可选的超时时间,需要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程

(4)属性介绍:

1 p.daemon:默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置
 
2 p.name:进程的名称
 
3 p.pid:进程的pid
 
4 p.exitcode:进程在运行时为None、如果为–N,表示被信号N结束(了解即可)
 
5 p.authkey:进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功(了解即可)
运行的状态)。timeout是可选的超时时间,需要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程

3.process类的使用

在window情况下,必须要用if __name__=='__main__':

(1)开启进程两种方式

方式一:普通方法,让进程中target调用函数

from multiprocessing import Process
import time
def work(name):
    print('%s is pao'%name)
    time.sleep(3)
    print('%s pao end'%name)

if __name__=='__main__':
    p=Process(target=work,args=('alex',))#args的括号内必须要有一个逗号
    p.start()
    print('zhu')

方式二:采用类的方法,在这个方法中一定要定义一个run的方法

from multiprocessing import Process
import time
class Work(Process):
    def __init__(self,name):
        super().__init__()#必须要继承父类的方法
        self.name=name
    def run(self):  #必须用定义一个run的函数
        print('%s is pao' %self.name)
        time.sleep(3)
        print('%s pao end' %self.name)

if __name__=='__main__':
    p=Work('egon')
    p.start()
    print('zhu')

(2).进程之间的内存空间是相互隔离的

from multiprocessing import Process
n=100      #在windows系统中应该把全局变量定义在if __name__ == '__main__'之上就可以了
def work():
    global n
    n=0
    print('子进程内: ',n)

if __name__ == '__main__':
    p=Process(target=work)
    p.start()
    print('主进程内: ',n)

3.使用多进程实现套接字功能

方式一:

import socket
from multiprocessing import Process
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
phone.bind(('127.0.0.1',8082))
phone.listen(5)
def talk(conn):
    while True: #通信循环
         try:
            data=conn.recv(1024) #最大收1024
            print(data.decode('utf-8'))
            if not data:break #针对linux
            conn.send(data.upper())
            # msg=input('===')  #在多进程的情况下,不允许服务端向客户端发送信息
            # print(msg)
            # conn.send(msg.encode('utf-8'))
         except Exception:
             break
    conn.close()
if __name__=='__main__':
    print('starting...')
    while True:
        conn,addr=phone.accept()
        print('IP:%s,PORT:%s' %(addr[0],addr[1]))
        p1 = Process(target=talk,args=(conn,))
        # p1 = Thread(target=talk, args=(conn,))
        p1.start()
#7、关机
phone.close()
服务端程序
import socket
#1、买手机
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#2、打电话
phone.connect(('127.0.0.1',8082))
#3、发收消息
while True:
    while True:
        msg=input('>>: ').strip()
        if not msg:continue
        phone.send(msg.encode('utf-8'))
        data=phone.recv(1024)
        print(data.decode('utf-8'))
#4、挂电话
phone.close()
客户端程序

方式二:

import socket
from multiprocessing import Process
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
phone.bind(('127.0.0.1',8082))
phone.listen(5)
class Commun(Process):
    def __init__(self,conn):
        super().__init__()
        self.conn=conn
    def run(self):
        while True: #通信循环
             try:
                data=self.conn.recv(1024) #最大收1024
                print(data.decode('utf-8'))
                if not data:break #针对linux
                self.conn.send(data.upper())
                # msg=input('===')  #在多进程的情况下,不允许服务端向客户端发送信息
                # print(msg)
                # conn.send(msg.encode('utf-8'))
             except Exception:
                 break
        self.conn.close()
if __name__=='__main__':
    print('starting...')
    while True:
        conn,addr=phone.accept()
        print('IP:%s,PORT:%s' %(addr[0],addr[1]))
        p=Commun(conn)
        p.start()
#7、关机
phone.close()
服务端程序
import socket
#1、买手机
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#2、打电话
phone.connect(('127.0.0.1',8082))
#3、发收消息
while True:
    while True:
        msg=input('>>: ').strip()
        if not msg:continue
        phone.send(msg.encode('utf-8'))
        data=phone.recv(1024)
        print(data.decode('utf-8'))
#4、挂电话
phone.close()
客户端程序

4.process对象的join方法

(1).普通的用法

from multiprocessing import Process
import time
def work(name):
    print('%s is pao'%name)
    time.sleep(3)
    print('%s pao end'%name)
if __name__=='__main__':
    p=Process(target=work,args=('alex',))#args的括号内必须要有一个逗号
    p.start()
    p.join()#join方法是主主进程在等子进程结束,join()括号内可以指定等待的时间
    # p.join(0.05)#这是就是等时间已过,主进程就会继续执行,而不会等到子进程结束
    print('zhu')

(2)使用方法

from multiprocessing import Process
import time
def work(name):
    print('%s is pao'%name)
    time.sleep(3)
    print('%s pao end'%name)
if __name__=='__main__':
    p1=Process(target=work,args=('alex',))#args的括号内必须要有一个逗号
    p2 = Process(target=work, args=('wpq',))
    p3 = Process(target=work, args=('egon',))
    
    '''串型的执行'''
    start=time.time()
    p1.start()
    p1.join()
    p2.start()
    p2.join()
    p3.start()
    p3.join()
    print('zhu')
    stop = time.time()
    print(stop - start)#9.338534116744995串型的执行时间
    
    '''并发的执行'''
    start = time.time()
    p1.start()
    p2.start()
    p3.start()
    p1.join()
    p2.join()
    p3.join()
    print('zhu')
    stop = time.time()
    print(stop-start)#3.1571805477142334#采用并发的方法的时间
join的串型与并发

4.process的其他方法

from multiprocessing import Process
import time
import random
class Piao(Process):
    def __init__(self,name):
        self.name=name
        super().__init__()
    def run(self):
        print('%s is piaoing' %self.name)
        time.sleep(random.randrange(1,5))
        print('%s is piao end' %self.name)
if __name__=='__main__':
    p1=Piao('egon1')
    p1.start()
    p1.terminate()#关闭进程,不会立即关闭,所以is_alive立刻查看的结果可能还是存活
    print(p1.is_alive()) #结果为True
    print('开始')
    time.sleep(1)
    print(p1.is_alive()) #结果为False
terminate与is_alive()方法
from multiprocessing import Process
import time
import random
class Piao(Process):
    def __init__(self,name):
        super().__init__()
        self.name=name

    def run(self):
        print('%s is piaoing' %self.name)
        time.sleep(random.randrange(1,3))
        print('%s is piao end' %self.name)


if __name__=='__main__':

    p=Piao('egon')
    p.start()
    print('开始')
    print(p.pid) #查看pid
name与pid

三.守护进程

 1.什么是守护进程?

守护进程是主进程创建的

守护进程的几点使用规则:

(1).主进程代码运行结束后,守护进程也会随着结束
(2).守护进程要在进程开启前就要定义好那个是守护进程
(3).守护进程内无法再开子进程,否则会抛出异常
守护进程一:
from multiprocessing import Process
import time
def work(name):
    print('%s is pao'%name)
    time.sleep(3)
    print('%s pao end'%name)

def work2(name):
    print('%s is pao' % name)
    time.sleep(3)
    print('%s pao end' % name)

if __name__=='__main__':
    p=Process(target=work,args=('alex',))
    p1 = Process(target=work2, args=('egon',))
    p.daemon=True
    p.start()
    p1.start()
    print('zhu')
 

结果:

守护进程二:

from multiprocessing import Process
import time
def work(name):
    print('%s is pao'%name)
    time.sleep(3)
    print('%s pao end'%name)

def work2(name):
    print('%s is pao' % name)
    time.sleep(3)
    print('%s pao end' % name)

if __name__=='__main__':
    p=Process(target=work,args=('alex',))
    p1 = Process(target=work2, args=('egon',))
    p.daemon=True
    p.start()
    p1.start()
    time.sleep(1)
    print('zhu')
    '''主进程结束后,守护进程p也会就会结束,
    如果主进程结束的时候, 守护进程也执行了打印操作,也会打印出来'''

结果为:

四.进程同步锁(互斥锁)

进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的,

竞争带来的结果就是错乱,如何控制,就是加锁处理

 

1.不加锁的情况(多个进程共享同一个打印终端)

'''不加锁的情况会出现数据混乱的情况'''
from multiprocessing import Process,Lock
import time,os
def work():
    print('%s is running'%os.getpid())
    time.sleep(1)
    print('%s is done'%os.getpid())
    
if __name__=='__main__':
    for i in range(3):
        p=Process(target=work)
        p.start()

结果如下:有点混乱

2.加锁的情况

'''加锁的情况,效率变慢,但是数据安全'''
from multiprocessing import Process,Lock
import time,os
def work(lock):
    # with lock:#简写锁
        lock.acquire()
        print('%s is running'%os.getpid())
        time.sleep(1)
        print('%s is done'%os.getpid())
        lock.release()

if __name__=='__main__':
    lock=Lock()
    for i in range(3):
        p=Process(target=work,args=(lock,))
        p.start()

结果:数据安全

 互斥锁总结:

#加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改,没错,速度是慢了,但牺牲了速度却保证了数据安全。
虽然可以用文件共享数据实现进程间通信,但问题是:
1.效率低(共享数据基于文件,而文件是硬盘上的数据)
2.需要自己加锁处理

#因此我们最好找寻一种解决方案能够兼顾:1、效率高(多个进程共享一块内存的数据)2、帮我们处理好锁问题。
这就是mutiprocessing模块为我们提供的基于消息的IPC通信机制:队列和管道。
1 队列和管道都是将数据存放于内存中 2 队列又是基于(管道+锁)实现的,可以让我们从复杂的锁问题中解脱出来, 我们应该尽量避免使用共享数据,尽可能使用消息传递和队列,避免处理复杂的同步和锁问题,而且在进程数目增多时,往往可以获得更好的可获展性。

五.队列

进程彼此之间互相隔离,要实现进程间通信(IPC),multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的

 1.创建队列的类(通过管道加锁的方式实现)

1 Queue([maxsize]):创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递。

2.参数介绍

1 maxsize是队列中允许最大项数,省略则无大小限制。

3.主要的方法:

1 q.put方法用以插入数据到队列中,put方法还有两个可选参数:blocked和timeout。如果blocked为True(默认值),
并且timeout为正值,该方法会阻塞timeout指定的时间,直到该队列有剩余的空间。如果超时,会抛出Queue.Full异常。
如果blocked为False,但该Queue已满,会立即抛出Queue.Full异常。
2 q.get方法可以从队列读取并且删除一个元素。同样,get方法有两个可选参数:blocked和timeout。
如果blocked为True(默认值),并且timeout为正值,那么在等待时间内没有取到任何元素,会抛出Queue.Empty异常。
如果blocked为False,有两种情况存在,如果Queue有一个值可用,则立即返回该值,否则,如果队列为空,则立即抛出Queue.Empty异常.
3 q.get_nowait():同q.get(False)
4 q.put_nowait():同q.put(False) 5 q.empty():调用此方法时q为空则返回True,该结果不可靠,比如在返回True的过程中,如果队列中又加入了项目。
8 q.full():调用此方法时q已满则返回True,该结果不可靠,比如在返回True的过程中,如果队列中的项目被取走。
9 q.qsize():返回队列中目前项目的正确数量,结果也不可靠,理由同q.empty()和q.full()一样

4.其他方法(了解):

1 q.cancel_join_thread():不会在进程退出时自动连接后台线程。可以防止join_thread()方法阻塞
2 q.close():关闭队列,防止队列中加入更多数据。调用此方法,后台线程将继续写入那些已经入队列但尚未写入的数据,
但将在此方法完成时马上关闭。如果q被垃圾收集,将调用此方法。关闭队列不会在队列使用者中产生任何类型的数据结束信号或异常。
例如,如果某个使用者正在被阻塞在get()操作上,关闭生产者中的队列不会导致get()方法返回错误。
3 q.join_thread():连接队列的后台线程。此方法用于在调用q.close()方法之后,等待所有队列项被消耗。
默认情况下,此方法由不是q的原始创建者的所有进程调用。调用q.cancel_join_thread方法可以禁止这种行为

使用方法:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
__author__ = 'Xufangming'
from multiprocessing import Queue,Process
import time,random
def work(q):
    q.put(random.randint(1,6))#往队列里面加数据
if __name__=='__main__':
    q=Queue(3)
    for i in range(3):
        p1=Process(target=work,args=(q,))#创建进程往对列里面加数据
        p1.start()
    time.sleep(1)
    print(q.full())#队列里面的数据满了
    print(q.get())
    print(q.get())
    print(q.get())
    print(q.empty())#队列中的数据空了
对列的使用方法

六.生产者和消费者模型

1.什么是生产者和消费者模型

生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。

2.为什么要引入使用生产者和消费者模型:

在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。

3.生产者和消费者模型的用处:

在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。

基于对列的生产者和消费者模型

'''生产者和消费者模型'''
from multiprocessing import Process,Queue
import time,random
def producer(name,q): #生产者
    for i in range(6):
        msg='馒头:%s'%i
        time.sleep(random.randint(1, 3))
        q.put(msg)
        # time.sleep(random.randint(1,3))
        print('厨师%s 生产了 %s'%(name,msg))
def consumer(name,q):
    for i in range(6):
        time.sleep(random.randint(1, 3))
        res=q.get()
        # time.sleep(random.randint(1, 3))
        print('%s 吃了 %s'%(name,res))
if __name__=='__main__':
    q=Queue()
    p=Process(target=producer,args=('egon',q))#实例化一个生产者的对象
    c=Process(target=consumer, args=('alex',q))#实例化一个消费者的对象
    p.start()
    c.start()
基于队列的生产者和消费者模型

生产者和消费者模型知识总结

#生产者消费者模型总结

    #程序中有两类角色
        一类负责生产数据(生产者)
        一类负责处理数据(消费者)
        
    #引入生产者消费者模型为了解决的问题是:
        平衡生产者与消费者之间的速度差
        
    #如何实现:
        生产者-》队列——》消费者
    #生产者消费者模型实现类程序的解耦和

 在这样的问题中就会产生一个问题:在生产者生产完后,生产者的任务就结束了,但是消费者在取完生产者的生产东西后,程序会一直处于死循环且卡在q.get()这一步

解决的办法就是在生产完成后,在往队列中再发一个结束的信号,这样消费者在接到结束信号后,就可以break出死循环。

 

七.进程池

1.什么是进程池?

进程池是通过multiprocessing模块中的pool模块来限制进程开启的个数。

2.为什么要用进程池?

我们知道,解决多个任务我们可以通过多个进程来解决,但是开启进程的消耗是很大的,当数量很少的时候,电脑可以开个多进程,但是当进程数有几万个,几十万个,这样的情况下,开启多个进程明显是不行的,这时候,我们就需要用进程池来解决这些问题,进程池中的个数是实现就设置好的,同一时刻就只有那几个进程在执行任务,当进程池中的某个进程执行完任务后,后面排队的任务就可以进入到进程池中,该进程就可一继续执行任务了。

3.使用方法

导入multiprocessing模块中pool,pool创建进程池的类

方法介绍:

1 Pool([numprocess  [,initializer [, initargs]]]):创建进程池

参数介绍:

1 numprocess:要创建的进程数,如果省略,将默认使用cpu_count()的值
2 initializer:是每个工作进程启动时要执行的可调用对象,默认为None
3 initargs:是要传给initializer的参数组

主要方法介绍:

1 p.apply(func [, args [, kwargs]]):在一个池工作进程中执行func(*args,**kwargs),然后返回结果。需要强调的是:此操作并不会在所有池工作进程中并执行func函数。
如果要通过不同参数并发地执行func函数,必须从不同线程调用p.apply()函数或者使用p.apply_async()
2 p.apply_async(func [, args [, kwargs]]):在一个池工作进程中执行func(*args,**kwargs),然后返回结果。
此方法的结果是AsyncResult类的实例,callback是可调用对象,接收输入参数。当func的结果变为可用时,将理解传递给callback。
callback禁止执行任何阻塞操作,否则将接收其他异步操作中的结果。
3 p.close():关闭进程池,防止进一步操作。如果所有操作持续挂起,它们将在工作进程终止前完成
4 P.jion():等待所有工作进程退出。此方法只能在close()或teminate()之后调用

 其他方法介绍:

方法apply_async()和map_async()的返回值是AsyncResul的实例obj。实例具有以下方法
obj.get():返回结果,如果有必要则等待结果到达。timeout是可选的。如果在指定时间内还没有到达,将引发一场。如果远程操作中引发了异常,它将在调用此方法时再次被引发。
obj.ready():如果调用完成,返回True
obj.successful():如果调用完成且没有引发异常,返回True,如果在结果就绪之前调用此方法,引发异常
obj.wait([timeout]):等待结果变为可用。
obj.terminate():立即终止所有工作进程,同时不执行任何清理或结束任何挂起工作。如果p被垃圾回收,将自动调用此函数

 应用一:同步调用

'''同步调用'''
from multiprocessing import Pool
import os,time,random
def work(i):
    print('%s is working'%os.getpid())
    time.sleep(random.randint(1,3))
    return i**2
if __name__=='__main__':
    p=Pool(4)#进程池从无到有创建4个进程,以后一直都是这几个进程执行任务,不会再去创建新的进程
    objs=[]
    for i in range(10):
        obj=p.apply(work,args=(i,))
        '''同步调用提交任务后会在原地等待任务结束,并且拿到结果后才回去执行下一个任务
        参数的括号内一定有有个逗号'''
        objs.append(obj)

    print(objs)

异步调用:

'''异步调用'''
from multiprocessing import Pool
import os,time,random
def work(i):
    print('%s is working'%os.getpid())
    time.sleep(random.randint(1,3))
    return i**2
if __name__=='__main__':
    p=Pool(4)
    # print(os.getpid())#主进程pid
    objs=[]
    for i in range(10):
        obj=p.apply_async(work,args=(i,))
        print(obj)
        objs.append(obj)
    p.close()#运行完毕要先关闭进程池
    p.join()#等进程池中所用的任务都运行完成后(主进程等)
    print(objs)
    for obj in objs:
        print(obj.get())
    print('')

应用二:基于进程池实现套接字通信

#!/usr/bin/env python
# -*- coding: utf-8 -*-
__author__ = 'Xufangming'
from multiprocessing import Pool
from socket import *
import os
server=socket(AF_INET,SOCK_STREAM)
server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
server.bind(('127.0.0.1',8086))
server.listen(5)
def talk(conn,addr):
    while True:
        print('id:%s'%os.getpid())
        try:
            data=conn.recv(1024)
            if not data:break #针对Linux系统
            '''在Linux系统中一旦客户端由于某种原因断开了连接,服务端这边不会抛出异常,
            而是会一直接受空,因为链接存在,所以就会一直接受空,如果不强行断开的话,
            就会一直处于死循环中'''
            print('ip:%s,port:%s'%(addr[0],addr[1]))
            conn.send(data.upper())
        except Exception:
            break
if __name__=='__main__':
    p=Pool(4)
    while True:
        conn,addr=server.accept()
        p.apply_async(talk,args=(conn,addr))
服务端
#!/usr/bin/env python
# -*- coding: utf-8 -*-
__author__ = 'Xufangming'
from socket import *
client=socket(AF_INET,SOCK_STREAM)
client.connect(('127.0.0.1',8086))
while True:
    msg=input('===>')
    if not msg:continue
    client.send(msg.encode('utf-8'))
    data=client.recv(1024)
    print(data.decode('utf-8'))
客户端

八.回调函数

1.回调函数运用场景

需要回调函数的场景:进程池中任何一个任务一旦处理完了,就立即告知主进程:我好了额,你可以处理我的结果了。主进程则调用一个函数去处理该结果,该函数即回调函数

我们可以把耗时间(阻塞)的任务放到进程池中,然后指定回调函数(主进程负责执行),这样主进程在执行回调函数时就省去了I/O的过程,直接拿到的是任务的结果。

这样的场景可以联想到爬虫的案例:

一个进程负责爬取页面的内容,回调函数负责处理和分析数据

from multiprocessing import Pool
import requests
import os

def get_page(url):
    print('<进程%s> get %s' %(os.getpid(),url))
    respone=requests.get(url)
    if respone.status_code == 200:
        return {'url':url,'text':respone.text}

def pasrse_page(res):
    print('<进程%s> parse %s' %(os.getpid(),res['url']))
    parse_res='url:<%s> size:[%s]\n' %(res['url'],len(res['text']))
    with open('db.txt','a') as f:
        f.write(parse_res)


if __name__ == '__main__':
    urls=[
        'https://www.baidu.com',
        'https://www.python.org',
        'https://www.openstack.org',
        'https://help.github.com/',
        'http://www.sina.com.cn/'
    ]

    p=Pool(3)
    res_l=[]
    for url in urls:
        res=p.apply_async(get_page,args=(url,),callback=pasrse_page)
        res_l.append(res)

    p.close()
    p.join()
    print([res.get() for res in res_l])
爬虫案例

 

posted @ 2017-10-16 23:19  明-少  阅读(190)  评论(0)    收藏  举报