一、multiprocessing模块介绍

python中的多线程无法利用CPU资源,在python中大部分情况使用多进程。python中提供了非常好的多进程包multiprocessing。

multiprocessing模块用来开启子进程,并在子进程中执行功能(函数),该模块与多线程模块threading的编程接口类似。

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

二、Process类的介绍

1.创建进程的类

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

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

2.参数介绍

group参数未使用,值始终为None

target表示调用对象,即子进程要执行的任务

args表示调用对象的位置参数元组,args=(1,2,'egon',)

kwargs表示调用对象的字典,kwargs={'name':'egon','age':18}

name为子进程的名称

3.方法介绍

p.start():启动进程,并调用该子进程中的p.run() 
p.run():进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法  

p.terminate():强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。
如果p还保存了一个锁那么也将不会被释放,进而导致死锁
p.is_alive():如果p仍然运行,返回True

p.join([timeout]):主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态)。timeout是可选的超时时间,
需要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程

4.属性介绍

p.daemon:默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置

p.name:进程的名称

p.pid:进程的pid

p.exitcode:进程在运行时为None、如果为–N,表示被信号N结束(了解即可)

p.authkey:进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功(了解即可)

三.Process类的使用

一定要把开进程的代码写在if __name__=='__main__':下面

开一个进程和主进程是并发的关系,我start一下就是先告诉操作系统我要开一个进程,然而它不会等待,他会去执行下面的代码,完了他吧进程开始后,就开始执行了

strat():方法的功能

  1.开启进程  2.执行功能

四.开启进程的两种方式

from multiprocessing import Process
import time
import random
def piao(name):
    print('%s is piaoing'%name)
    time.sleep(random.randint(1,3))
    print('%s is piao end'%name)
if __name__ =='__main__':
    p1 = Process(target=piao,kwargs={'name':'alex'})
    p2 = Process(target=piao,kwargs={'name':'alex'})
    p3 = Process(target=piao,kwargs={'name':'alex'})
    p1.start()
    p2.start()
    p3.start()
    print('主进程')

第一种方式

第一种方式

from multiprocessing import Process
import time
import random
import os
class Piao(Process):
    def __init__(self,name):
        super().__init__() #必须继承父类的一些属性
        self.name = name
    def run(self):  #必须得实现一个run方法
        print(os.getppid(),os.getpid())
        print('%s is piaoing'%self.name)
        time.sleep(random.randint(1,3))
        print('%s is piao end'%self.name)
if __name__ =='__main__':
    p1 = Piao('alex')
    p2 = Piao('wupeiqi')
    p3 = Piao('yuanhao')
    p1.start()
    p2.start()
    p3.start()
    print('主进程',os.getpid())

第二种方式

第二种方式

getppid()父进程idgetpid() #当前进程id

五.多进程实现套结并发

from socket import *
from multiprocessing import Process
s = socket(AF_INET,SOCK_STREAM)
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind(('127.0.0.1',8081))
s.listen(5)
print('start running...')
def talk(coon,addr):
    while True:
        try:
            data = coon.recv(1024)
            if not data: break
            coon.send(data.upper())
        except Exception:
            break
    coon.close()

if __name__ == '__main__':
    while True:
        coon,addr = s.accept()
        print(coon,addr)
        p =Process(target=talk,args=(coon,addr))
        p.start()
    s.close()

服务端

服务端

from socket import *
c = socket(AF_INET,SOCK_STREAM)
c.connect(('127.0.0.1',8081))
while True:
    cmd = input('>>:').strip()
    if not cmd:continue
    c.send(cmd.encode('utf-8'))
    data = c.recv(1024)
    print(data.decode('utf-8'))
c.close()

客户端

客户端

 

============================part2:Process对象的其他方法或属性

进程对象的其他方法一:terminate,is_alive

#进程对象的其他方法一:terminate,is_alive
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)


p1=Piao('egon1')
p1.start()

p1.terminate()#关闭进程,不会立即关闭,所以is_alive立刻查看的结果可能还是存活
print(p1.is_alive()) #结果为True

print('开始')
print(p1.is_alive()) #结果为False

 

注意: p.join(),是父进程在等待p的结束,是父进程阻塞在原地,而p任然在后台运行

#进程对象的其他方法二:p.daemon=True,p.join

#进程对象的其他方法二:p.daemon=True,p.join
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,3))
        print('%s is piao end' %self.name)


p=Piao('egon')
p.daemon=True #一定要在p.start()前设置,设置p为守护进程,禁止p创建子进程,并且父进程死,p跟着一起死
p.start()
p.join(0.0001) #等待p停止,等0.0001秒就不再等了
print('开始')

#进程对象的其他属性:name,pid

#进程对象的其他属性:name,pid
from multiprocessing import Process
import time
import random
class Piao(Process):
    def __init__(self,name):
        # self.name=name
        # super().__init__() #Process的__init__方法会执行self.name=Piao-1,
        #                    #所以加到这里,会覆盖我们的self.name=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)

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

 

六.进程同步(锁)

进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的,
part1:共享同一打印终端,发现会有多行内容打印到一行的现象(多个进程共享并抢占同一个打印终端,会乱了)

#多进程共享一个打印终端(用python2测试看两个进程同时往一个终端打印,出现打印到一行的错误)
from multiprocessing import Process
import time
class Logger(Process):
    def __init__(self):
        super(Logger,self).__init__()
    def run(self):
        print(self.name)


for i in range(1000000):
    l=Logger()
    l.start()

part2:共享同一个文件,你是否会想到,既然可以用文件共享数据,那么进程间通信用文件作为数据传输介质就可以了啊,可以,但是有问题:1.效率 2.需要自己加锁处理

#多进程共享一套文件系统
from multiprocessing import Process
import time,random

def work(f,msg):
    f.write(msg)
    f.flush()


f=open('a.txt','w') #在windows上无法把f当做参数传入,可以传入一个文件名,然后在work内用a+的方式打开文件,进行写入测试
for i in range(5):
    p=Process(target=work,args=(f,str(i)))
    p.start()

需知:加锁的目的是为了保证多个进程修改同一块数据时,同一时间只能有一个修改,即串行的修改,没错,速度是慢了,牺牲了速度而保证了数据安全。

进程之间数据隔离,但是共享一套文件系统,因而可以通过文件来实现进程直接的通信,但问题是必须自己加锁处理

所以,就让我们帮文件当做数据库,模拟抢票(Lock互斥锁)

#文件db的内容为:{"count":1}
#注意一定要用双引号,不然json无法识别
from multiprocessing import Process,Lock
import json
import time
import random
import os
def search():
    dic=json.load(open('db.txt',))
    print('剩余票数%s' %dic['count'])

def get_ticket():
    dic=json.load(open('db.txt',))
    if dic['count'] > 0:
        dic['count']-=1
        json.dump(dic,open('db.txt','w'))
        print('%s 购票成功' %os.getpid())
def task(mutex):
    search()
    time.sleep(random.randint(1, 3)) #模拟购票一系列繁琐的过程所花费的时间
    mutex.acquire()
    get_ticket()
    mutex.release()
if __name__ == '__main__':
    mutex=Lock()
    for i in range(50):
        p=Process(target=task,args=(mutex,))
        p.start()

模拟抢票

 

 

七.进程之间的通信(IPC)方式一:队列(推荐使用)    方式二:管道

创建队列的类(底层就是以管道和锁定的方式实现)

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

 

参数介绍

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

 

方法介绍

主要方法

q.put方法用以插入数据到队列中,put方法还有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,该方法会阻塞timeout指定的时间,直到该队列有剩余的空间。如果超时,会抛出Queue.Full异常。如果blocked为False,但该Queue已满,会立即抛出Queue.Full异常。
q.get方法可以从队列读取并且删除一个元素。同样,get方法有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,那么在等待时间内没有取到任何元素,会抛出Queue.Empty异常。如果blocked为False,有两种情况存在,如果Queue有一个值可用,则立即返回该值,否则,如果队列为空,则立即抛出Queue.Empty异常.
 
q.get_nowait():同q.get(False)
q.put_nowait():同q.put(False)

q.empty():调用此方法时q为空则返回True,该结果不可靠,比如在返回True的过程中,如果队列中又加入了项目。
q.full():调用此方法时q已满则返回True,该结果不可靠,比如在返回True的过程中,如果队列中的项目被取走。
q.qsize():返回队列中目前项目的正确数量,结果也不可靠,理由同q.empty()和q.full()一样

其他方法(了解)

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

应用

'''
multiprocessing模块支持进程间通信的两种主要形式:管道和队列
都是基于消息传递实现的,但是队列接口
'''

from multiprocessing import Process,Queue
import time
q=Queue(3)


#put ,get ,put_nowait,get_nowait,full,empty
q.put(3)
q.put(3)
q.put(3)
print(q.full()) #满了

print(q.get())
print(q.get())
print(q.get())
print(q.empty()) #空了

 

生产者和消费者模型

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

 

为什么要使用生产者和消费者模式

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

什么是生产者消费者模式

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

 基于队列实现生产者消费者模型

from multiprocessing import Process,Queue
import time,random,os

def consumer(q):
    while True:
        time.sleep(random.randint(1,3))
        res=q.get()
        print('\033[45m消费者拿到了:%s\033[0m' %res)

def producer(seq,q):
    for item in seq:
        time.sleep(random.randint(1,3))
        print('\033[46m生产者生产了:%s\033[0m' %item)

        q.put(item)

if __name__ == '__main__':
    q=Queue()

    seq=('包子%s' %i for i in range(10))
    c=Process(target=consumer,args=(q,))
    c.start()
    producer(seq,q)

    print('主线程')

from multiprocessing import Process,Queue
import time,random,os


def consumer(q):
    while True:
        time.sleep(random.randint(1,3))
        res=q.get()
        if res is None:break
        print('\033[45m消费者拿到了:%s\033[0m' %res)

def producer(seq,q):
    for item in seq:
        time.sleep(random.randint(1,3))
        print('\033[46m生产者生产了:%s\033[0m' %item)

        q.put(item)

if __name__ == '__main__':
    q=Queue()

    c=Process(target=consumer,args=(q,))
    c.start()

    producer(('包子%s' %i for i in range(10)),q)
    q.put(None)
    c.join()
    print('主线程')

主线程等待消费者结束(生产者发送结束信号给消费者)

主线程等待消费者结束(生产者发送结束信号给消费者)