并发编程-进程

操作系统发展史

操作系统的三种基本类型:多道批处理系统、分时系统、实时系统。

  • 联机批处理系统
  • 脱机批处理系统
  • 单处理机多道程序系统
    • 当一道程序因I/O请求而暂停运行时,CPU便立即转去运行另一道程序。
    • 宏观上并行
    • 微观上串行
  • 分时系统
    • 分时技术:把处理机的运行时间分成很短的时间片,按时间片轮流把处理机分配给各联机作业使用。
    • 分时系统的主要目标:对用户响应的及时性,即不至于用户等待每一个命令的处理时间过长。
  • 实时系统
    • 实时控制系统。当用于飞机飞行、导弹发射等的自动控制
    • 实时信息处理系统。当用于预定飞机票、查询有关航班、航线、票价等事宜
  • 通用操作系统
    • 通用操作系统:具有多种类型操作特征的操作系统。可以同时兼有多道批处理、分时、实时处理的功能,或其中两种以上的功能。
  • 操作系统的进一步发展
    • 个人计算机操作系统
    • 网络操作系统
    • 分布式操作系统
      • 负责全系统的资源分配和调度、任务划分、信息传输和控制协调工作,并为用户提供一个统一的界面。
  • 操作系统应该分成两部分功能:
    • 隐藏了丑陋的硬件调用接口,为应用程序员提供调用硬件资源的更好,更简单,更清晰的模型(系统调用接口)。
    • 将应用程序对硬件资源的竞态请求变得有序化

[[操作系统就是一个协调、管理和控制计算机硬件资源和软件资源的控制程序]]

进程
  • 进程
    • 进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。
    进程 Process id = pid
    程序 -->进程
    进程就是运行中的程序
    pycharm pid
    pid是一个全系统唯一的对某个进程的标识
    随着这个进程的重启 pid可能会变化

进程与进程之间的关系 : 数据隔离的

  • 注意:同一个程序执行两次,就会在操作系统中出现两个进程,所以我们可以同时运行一个软件,分别做不同的事情也不会混乱。
  • 进程调度

    • 先来先服务(FCFS)调度算法是一种最简单的调度算法,该算法既可用于作业调度,也可用于进程调度。
    • 短作业(进程)优先调度算法(SJ/PF)是指对短作业或短进程优先调度的算法,该算法既可用于作业调度,也可用于进程调度。
    • 时间片轮转(Round Robin,RR)法的基本思路是让每个进程在就绪队列中的等待时间与享受服务的时间成比例。
    • 而多级反馈队列调度算法则不必事先知道各种进程所需的执行时间,而且还可以满足各种类型进程的需要,因而它是目前被公认的一种较好的进程调度算法。在采用多级反馈队列调度算法的系统中,调度算法的实施过程如下所述。
  • 进程的并行与并发

      进程的并行与并发
      并行 : 并行是指两者同时执行,比如赛跑,两个人都在不停的往前跑;(资源够用,比如三个线程,四核的CPU )
    
      并发 : 并发是指资源有限的情况下,两者交替轮流使用资源,比如一段路(单核CPU资源)同时只能过一个人,A走一段后,让给B,B用完继续给A ,交替使用,目的是提高效率。
    
      区别:
    
      并行是从微观上,也就是在一个精确的时间片刻,有不同的程序在执行,这就要求必须有多个处理器。
      并发是从宏观上,在一个时间段上可以看出是同时执行的,比如一个服务器同时处理多个session。
    
  • 进程三状态

      (1)就绪(Ready)状态当进程已分配到除CPU以外的所有必要的资源,只要获得处理机便可立即执行,这时的进程状态称为就绪状态。
    
      (2)执行/运行(Running)状态当进程已获得处理机,其程序正在处理机上执行,此时的进程状态称为执行状态。
    
      (3)阻塞(Blocked)状态正在执行的进程,由于等待某个事件发生而无法执行时,便放弃处理机而处于阻塞状态。引起进程阻塞的事件可有多种,例如,等待I/O完成、申请缓冲区不能满足、等待信件(信号)等。
    
  • 同步/异步与阻塞/非阻塞
    同步 两件事不一起做 一件事做完 再做另一件事
    异步 两件事一起做

同步阻塞 :不能充分利用CPU
异步非阻塞 : 过度利用CPU

multiprocess模块

if name == 'main': 只是在windows上必须写(在Windows操作系统中由于没有fork(linux操作系统中创建进程的机制),在创建子进程的时候会自动 import 启动它的这个文件,而在 import 的时候又执行了整个文件。因此如果将process()直接写在文件中就会无限递归创建子进程报错。所以必须把创建子进程的部分使用if name ==‘main’ 判断保护起来,import 的时候 ,就不会递归运行了。)

process模块是一个创建进程的模块,借助这个模块,就可以完成进程的创建。

参数介绍:
1 group参数未使用,值始终为None
2 target表示调用对象,即子进程要执行的任务
3 args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号
4 kwargs表示调用对象的字典,kwargs={'name':'egon','age':18}
5 name为子进程的名称
方法介绍:
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开启的进程
属性介绍:
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字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功(了解即可)
  • 进程之间数据隔离问题
import os
import time
from multiprocessing import Process
count = 100
def func():
    global count
    count -= 1
    print('子进程 :',count)

if __name__ == '__main__':
    print('主进程',os.getpid(),os.getppid())
    p = Process(target=func)
    p.start()
    time.sleep(2)
    print('主进程 :',count)
  • 启动多个进程
from multiprocessing import Process
def func(arg): # 能给子进程中传参数 arg
    print('子进程%s :'%arg ,os.getpid(),os.getppid())


if __name__ == '__main__':
    for i in range(10):
        Process(target=func,args=(i,)).start()
  • 子进程和父进程之间的关系

1.父进程和子进程的启动时异步的
父进程只负责通知操作系统启动子进程
接下来的工作由操作系统接手 父进程继续执行

2.父进程执行完毕之后并不会直接结束程序,
而是会等待所有的子进程都执行完毕之后才结束
父进程要负责回收子进程的资源

import time
from multiprocessing import Process
def func(arg):
    print('子进程%s :'%arg ,os.getpid(),os.getppid())
    time.sleep(5)
    print('子进程end')


if __name__ == '__main__':
    for i in range(10):
        Process(target=func,args=(i,)).start()
    print('父进程*******')

小作业:
并发的socket基于tcp server

# server端
import socket
from multiprocessing import Process
def communicate(conn):
    while True:
        conn.send(b'hello')
        print(conn.recv(1024))

if __name__ == '__main__':
    sk = socket.socket()
    sk.bind(('127.0.0.1',9000))
    sk.listen()
    while True:
        conn,addr = sk.accept()
        Process(target=communicate,args=(conn,)).start()

# client端
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',9000))
while True:
    print(sk.recv(1024))
    sk.send(b'bye')

总结:

操作系统:多道、分时、实时
同步异步
    同步 一件事情做完再做另一件事
    异步 同时做多件事
阻塞和非阻塞
    阻塞 :recv accept recvfrom input sleep
        会让整个进程进入阻塞队列
    非阻塞 :进程只会在就绪和运行状态中切换
进程三状态 :就绪 运行 阻塞
并发并行 :
    并发是包含并行的
    并发 :宏观上多个程序同时运行
    并行 :微观上多个程序在一个时间片内在cpu上同时工作
子进程和父进程
    os.getpid os.getppid
进程 是操作系统中最小的 资源分配单位
进程
    multiprocessing
    multiprocessing.Process
    如何开启一个子进程
    主进程
    主进程和子进程之间的关系:
        主进程会等待子进程全部执行结束才结束
    各个进程之间有什么特点:
        异步
        数据隔离

多个子进程的执行顺序不是根据启动顺序决定的

jion()用法(主线程等待p终止,等待状态,阻塞状态)

第一种

from multiprocessing import Process
def func(index):
    print('第%s个进程执行完毕'%index)

if __name__ == '__main__':
    p_lst = []
    for i in range(10):
        p = Process(target=func,args=(i,))
        p.start()
        p_lst.append(p)
    for x in p_lst:x.join() # 阻塞 直到p进程执行完毕就结束阻塞 
    print('进程启动完了')

第二种:继承Process类的形式开启进程的方式

# 开启进程的第二种方式 给子进程传参数
class MyProcess(Process):
    def __init__(self,arg):
        super().__init__()
        self.arg = arg
    def run(self):
        # time.sleep(1)
        print('子进程:',os.getpid(),os.getppid(),self.arg)

if __name__ == '__main__':
    # p = MyProcess('参数')
    # p.start()   # 开启一个子进程,让这个子进程执行run方法
    # p.join()
    # print('主进程:',os.getpid())
    p_lst = []
    for i in range(10):
        p = MyProcess('参数%s'%i)
        p.start()  # 开启一个子进程,让这个子进程执行run方法
        p_lst.append(p)
    for p in p_lst:
        p.join()
    print('主进程:', os.getpid())
守护进程

设置p为一个守护进程,必须在start之前完成

正常情况下,主进程会等待子进程完全结束才结束
守护进程会随着主进程的代码执行完毕而结束
import time
from multiprocessing import Process

def func():
    print('子进程 start')
    time.sleep(3)
    print('子进程 end')

if __name__ == '__main__':
    p = Process(target=func)
    p.daemon = True     # 设置p为一个守护进程,必须在start之前完成
    p.start()
    time.sleep(2)
    print('主进程')

1,如果主进程代码已经执行完毕,但是子进程还没执行完,守护进程都不会继续执行
2,守护进程会随着主进程的代码执行完毕而结束
3,主进程会等待子进程结束,守护进程只等待主进程代码结束就结束了

用途:

程序的报活
主程序
守护进程 :每隔五分钟就向一台机器汇报自己的状态

def func():
    while True:
        time.sleep(5*60)
        print('我还活着')

def main():
    print('主程序会7*24小时的提供服务')

# 10个服务
# 第11个服务 : 检测其它10个服务是否还活着
锁 (普通锁,互斥锁)
加锁
降低了程序的效率,让原来能够同时执行的代码变成顺序执行了,异步变同步的过程
保证了数据的安全
在异步的情况下,多个进程又可能同时修改同一份资源
就给这个修改的过程加上锁
加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改
port time
import json
from multiprocessing import Process,Lock
def search(person):
    with open('ticket') as f:
        dic = json.load(f)
    time.sleep(0.2) #网络延迟
    print('%s查询余票 :'%person,dic['count'])

def get_ticket(person):
    with open('ticket') as f:
        dic = json.load(f)
    time.sleep(0.2)  #网络延迟
    if dic['count'] > 0:
        print('%s买到票了'%person)
        dic['count'] -= 1
        time.sleep(0.2)
        with open('ticket','w') as f:
            json.dump(dic,f)
    else:
        print('%s没买到票'%person)

def ticket(person,lock):
    search(person)
    lock.acquire()
    get_ticket(person)
    lock.release()

if __name__ == '__main__':
    lock = Lock()
    for i in range(10):
        p = Process(target=ticket,args=('person%s'%i,lock))
        p.start()
        # p.join()

锁模板:

import time
from multiprocessing import Process,Lock
def func(num,lock):
    time.sleep(1)
    print('异步执行',num)
    lock.acquire()
    time.sleep(0.5)
    print( '同步执行',num)
    lock.release()

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

  • 互斥锁Lock lock.acquire()和lock.release()是一对一般是一对
lock = Lock()
lock.acquire()
print('456')
# lock.release()
lock.acquire()  #等待lock.release()  同一个进程不向下执行
print('123')
信号量
信号量的实现机制:计数器 + 锁实现的

互斥锁同时只允许一个线程更改数据,而信号量Semaphore是同时允许一定数量的线程更改数据 。
假设商场里有4个迷你唱吧,所以同时可以进去4个人,如果来了第五个人就要在外面等待,等到有人出来才能再进去玩。
实现:
信号量同步基于内部计数器,每调用一次acquire(),计数器减1;每调用一次release(),计数器加1.当计数器为0时,acquire()调用被阻塞。这是迪科斯彻(Dijkstra)信号量概念P()和V()的Python实现。信号量同步机制适用于访问像服务器这样的有限资源。
信号量与进程池的概念很像,但是要区分开,信号量涉及到加锁的概念
import time,random
from multiprocessing import Process,Semaphore
def func(person,sem):
    sem.acquire()
    print('%s走进ktv'%person)
    time.sleep(random.randint(3,5))
    print('%s走出ktv'%person)
    sem.release()

if __name__ == '__main__':
    sem = Semaphore(4)
    for i in range(10):
        p = Process(target=func,args=('person%s'%i,sem))
        p.start()
事件
Event
阻塞事件 :wait()方法
    wait是否阻塞是看event对象内部的一个属性

控制这个属性的值
    set() 将这个属性的值改成True
    clear()  将这个属性的值改成False
    is_set() 判断当前的属性是否为True
import time
import random
from multiprocessing import Process,Event
def traffic_light(e):
    print('\033[31m红灯亮\033[0m')
    while True:
        if e.is_set():
            time.sleep(2)
            print('\033[31m红灯亮\033[0m')
            e.clear()

        else:
            time.sleep(2)
            print('\033[32m绿灯亮\033[0m')
            e.set()


def car(e,i):
    if not e.is_set():
        print('car %s 在等待' % i)
        e.wait()  # wait是否阻塞是看event对象内部的一个属性
    print('car %s 通过了'%i)

if __name__ == '__main__':
    e = Event()   # 开始e的e.is_set()属性就是False
    p = Process(target=traffic_light,args=(e,))
    p.daemon = True  # 不用这一步会一直循环"红灯亮绿灯亮"  让while True随着主进程的结束而结束
    p.start()
    p_lst = []
    for i in  range(20):
        time.sleep(random.randrange(0,3,2))
        p = Process(target=car,args=(e,i))
        p.start()
        p_lst.append(p)
    for p in p_lst:p.join()  # 防止p.daemon = True导致的红绿灯进程结束而使car一直陷入等待状态

总结:

Process:
    创建进程对象
        Process
        MyProcess
    方法:
        daemon   属性True 表示这个子进程为守护进程
        start()  开启一个子进程
        join()   阻塞直到子进程结束
Lock:
    锁 互斥锁
    在进程之间保证数据安全性
    lock.acquire()
    lock.release()
Semaphore:
    计数器+锁
    
Event 事件:
    内部有一个信号控制wait的阻塞事件
    控制这个信号:
        set()
        clear()
        is_set()

进程之间的通信:
多个进程之间有一些固定的通信内容
一些信号
socket 给予文件家族通信

进程之间虽然内存不共享,但是是可以通信的
Lock Semaphore Event 都在进行进城之间的通信
只不过这些通信的内容我们不能改变

进程间通信

IPC(Inter-Process Communication,译:进程间通信)
队列: 先进先出

from multiprocessing import Queue
import queue
q = Queue(2)  # 创建共享的进程队列
q.put(1)
q.put(2)
q.put(3) # 加不进去会陷入阻塞
print(q.get())
print(q.get())
print(q.get())  # 取不出来会阻塞

try:
    print(q.get_nowait())   #取不出来不阻塞  会报错
except queue.Empty:
    print('empty')

q.put_nowait(3)  # 放不进去 会丢失数据 然后报错

print(q.empty())  # 队列为空时 True  (不可靠)
print(q.full())   # 队列为满是True
print(q.qsize()) # 队列中目前项目的正确数量

队列:

# 队列中进程间的通信 , 底层实现有socket 
from multiprocessing import Process,Queue

def consume(q):
    print('son-->',q.get())
    q.put('abc')

if __name__ == '__main__':
    q = Queue()
    p = Process(target=consume,args=(q,))
    p.start()
    q.put({'123':123})   # 先启动父进程  在延迟一一会启动子进程
    p.join()   # 或者 time.sleep()  等同
    print('Foo-->',q.get())

  • 生产者消费者模型:

    • 在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。
import time
import random
from multiprocessing import Process,Queue
def consumer(q,name):
    # 处理数据(消费者)
    while True:
        food = q.get()
        if food is None:break
        time.sleep(random.random())
        print('%s吃了一个%s'%(name,food))

def producer(q,name,food):
    # 获取数据(生产者)
    for i in range(10):
        time.sleep(random.random())
        print('%s生产了%s%s' % (name, food,i))
        q.put(food+str(i))


if __name__ == '__main__':
    q = Queue()
    c1 = Process(target=consumer,args=(q,'Alex'))
    c2 = Process(target=consumer,args=(q,'小猪'))
    c1.start()
    c2.start()
    p1 = Process(target=producer, args=(q, '小杨', '泔水'))
    p2 = Process(target=producer, args=(q, '小何', '碧池'))
    p1.start()
    p2.start()
    p1.join()
    p2.join()
    q.put(None)   # 有几个consumer就需要放几个None
    q.put(None)
  • Joinablequeue模块(生产者消费者模型)

      JoinableQueue的实例p除了与Queue对象相同的方法之外,还具有以下方法:
    
      q.task_done() 
      使用者使用此方法发出信号,表示q.get()返回的项目已经被处理。如果调用此方法的次数大于从队列中删除的项目数量,将引发ValueError异常。
    
      q.join() 
      生产者将使用此方法进行阻塞,直到队列中所有项目均被处理。阻塞将持续到为队列中的每个项目均调用q.task_done()方法为止。 
      下面的例子说明如何建立永远运行的进程,使用和处理队列上的项目。生产者将项目放入队列,并等待它们被处理。
    
import time
import random
from multiprocessing import Process,JoinableQueue
def consumer(q,name):
    # 处理数据
    while True:
        food = q.get()
        time.sleep(random.uniform(0.5,1))
        print('%s吃了一个%s' % (name, food))
        q.task_done()   # 通知队列计数减一  通知队列已经有一个数据被处理了

def producer(q,name,food):
    # 获取数据
    for i in range(10):
        time.sleep(random.uniform(0.3,0.8))
        print('%s生产了%s%s'%(name,food,i))
        q.put(food+str(i))

if __name__ == '__main__':
    jq = JoinableQueue()
    c1 = Process(target=consumer,args=(jq,'alex'))
    c2 = Process(target=consumer,args=(jq,'wusir'))
    c1.daemon = True    # consumer 不会结束  会随着主进程代码结束而结束
    c2.daemon = True   
    c1.start()
    c2.start()
    p1 = Process(target=producer,args=(jq,'杨宗河','泔水'))
    p2 = Process(target=producer,args=(jq,'何思浩','鱼刺和骨头'))
    p1.start()
    p2.start()
    p1.join()   # 生产者要生产完该生产的
    p2.join()
    jq.join()   # 直达队列为0

# q.join() # 阻塞直到放入队列中所有的数据都被处理掉(有多少个数据就接收到了多少task_done)
  • 管道
    队列是基于管道实现的
    管道是基于socket实现的
    管道 + 锁 简便的IPC机制 使得 进程之间数据安全
    管道 进城之间数据不安全 且存取数据复杂
import time
from multiprocessing import Pipe,Process
# 1
left,right = Pipe()
left.send(12345)  # 发
print(right.recv())  # 收


# 2
def consumer(left,right):
    time.sleep(1)
    print(right.recv())

if __name__ == '__main__':
    left,right = Pipe()
    Process(target=consumer,args=(left,right)).start()
    left.send(1234)
    
# 3 进程间通信 

def consumer(left,right):
    left.close()
    while True:
        try:
            print(right.recv())
        except EOFError:
            break

if __name__ == '__main__':
    left,right = Pipe()
    Process(target=consumer,args=(left,right)).start()
    right.close()
    for i in range(10):
        left.send('泔水%s'%i)
    left.close()
pipe的端口管理不会随着某一个进程的关闭就关闭
操作系统来管理进程对这些端口的使用
left,right
left,right
操作系统管理4个端口  每关闭一个端口计数-1,直到所有的端口都关闭了,
剩余1个端口的时候 recv就会报错

应该特别注意管道端点的正确管理问题。如果是生产者或消费者中都没有使用管道的某个端点,就应将它关闭。这也说明了为何在生产者中关闭了管道的输出端,在消费者中关闭管道的输入端。如果忘记执行这些步骤,程序可能在消费者中的recv()操作上挂起。管道是由操作系统进行引用计数的,必须在所有进程中关闭管道后才能生成EOFError异常。因此,在生产者中关闭管道不会有任何效果,除非消费者也关闭了相同的管道端点

  • Manager实现数据共享 ____&&____

      进程间数据是独立的,可以借助于队列或管道实现通信,二者都是基于消息传递的
      虽然进程间数据独立,但可以通过Manager实现数据共享,事实上Manager的功能远不止于此
    
from multiprocessing import Manager,Process,Lock
def work(d,lock):
    with lock: #不加锁而操作共享的数据,肯定会出现数据错乱
        d['count']-=1

if __name__ == '__main__':
    lock=Lock()
    with Manager() as m:
        dic=m.dict({'count':100})   # 字典dict  还有list 方法  不安全,需要加锁
        p_l=[]
        for i in range(100):
            p=Process(target=work,args=(dic,lock))
            p_l.append(p)
            p.start()
        for p in p_l:
            p.join()
        print(dic)
把所有实现了数据共享的比较便捷的类都重新又封装了一遍,并且在原有的multiprocessing基础上
增加了新的机制 list dict

数据共享的机制
    支持数据类型非常有限
    list dict都不是数据安全的,你需要自己加锁来保证数据安全
from multiprocessing import Manager,Process,Lock
def work(d,lock):
    with lock: # 自动取钥匙和还钥匙(锁)  python的上下文管理
        d['count']-=1

if __name__ == '__main__':
    lock=Lock()
    with Manager() as m:  # 相当于  m = Manager()  python的上下文管理
        dic=m.dict({'count':100})
        p_l=[]
        for i in range(100):
            p=Process(target=work,args=(dic,lock))
            p_l.append(p)
            p.start()
        for p in p_l:
            p.join()
        print(dic)
  • 进程池
    开启过多的进程并不能提高你的效率

      计算密集型  充分占用CPU 多进程可以充分利用多核
          适合开启多进程,但是不适合开启很多多进程
      IO密集型    大部分时间都在阻塞队列,而不是在运行状态中
          根本不太适合开启多进程
    
      信号量
          500件衣服    任务
          500个人      进程
          只有4台机器  CPU
    
      多进程
          500件衣服
          500个人
          抢4台机器
    
      进程池
          500件衣服
          4个人
          4台机器
    
  • 进程池和多进程比较

import time
from multiprocessing import Pool,Process

def func(num):
    print('做了第%s件衣服'%num)

if __name__ == '__main__':
    start = time.time()
    p = Pool(4)   # 没有值则使用默认cpu_count()的值
    for i in range(100):
        p.apply_async(func,args=(i,))  # 异步提交func到一个子进程中执行
    p.close()    # 关闭进程池,用户不能再向这个池中提交任务了
    p.join()     # 阻塞,直到进程池中所有的任务都被执行完
    print(time.time() - start)   # 进程池时间少

    start = time.time()
    p_lst = []
    for i in range(100):
        p = Process(target=func,args=(i,))
        p.start()
        p_lst.append(p)
    for p in p_lst:p.join()
    print(time.time() - start)   # 多进程时间多
提交任务
同步提交 apply
    返回值 : 子进程对应函数的返回值
    一个一个顺序执行的,并没有任何并发效果
异步提交 apply_async
    没有返回值,要想所有任务能够顺利的执行完毕
        p.close() 关闭进程池,防止进一步操作。如果所有操作持续挂起,它们将在工作进程终止前完成
        p.join() # 必须先close再join,阻塞直到进程池中的所有任务都执行完毕
        没有p.close()和p.join()子进程将不被执行就结束因为父进程和子进程完全异步--->apply_async()方法
    有返回值的情况下
        res.get() # get不能再提交任务之后立刻执行,应该是先提交所有的任务再通过get获取结果
    map()方法
        异步提交的简化版本
        自带close和join方法
import os
import time
from multiprocessing import Pool
# 同步提交  apply()方法
def task(num):
    time.sleep(1)
    print('%s : %s'%(num,os.getpid()))
    return num**2

if __name__ == '__main__':
    p = Pool()  # 不写默认cpu个数 os.cpu_count()
    for i in range(20):
        res = p.apply(task,args=(i,))   # 提交任务的方法 同步提交
        print('-->',res)                # 有IPC机制


# 异步提交 无返回值(取不到返回值) apply_async()
def task(num):
    time.sleep(1)
    print('%s : %s'%(num,os.getpid()))
    return num**2

if __name__ == '__main__':
    p = Pool()
    for i in range(20):
        p.apply_async(task,args=(i,))   # 提交任务的方法 异步提交
    p.close()
    p.join()

# 异步提交 有返回值
def task(num):
    time.sleep(1)
    print('%s : %s'%(num,os.getpid()))
    return num**2

if __name__ == '__main__':
    p = Pool()
    res_lst = []
    for i in range(20):
        res = p.apply_async(task,args=(i,))   # 提交任务的方法 异步提交
        res_lst.append(res)
    for res in res_lst:
        print(res.get())              #不用close 和join

# map()方法
def task(num):
    time.sleep(1)
    print('%s : %s'%(num,os.getpid()))
    return num**2

if __name__ == '__main__':
    p = Pool()
    p.map(task,range(20))

内容总结:
进城之间通信 IPC

IPC:数据安全、先进先出、基于管道+锁实现的
    生产者消费者模型
    JoinableQueue
管道 :
    Pipe :socket pickle
    管道是队列的底层,并且数据不安全

池 Pool multiprocessing起进程池
不能传队列作为子进程的参数,只能传管道
进程池中开启的个数 :默认是cpu的个数

提交任务
    apply 同步提交
        直接返回结果
    apply_async 异步提交
        不会直接返回一个结果 而是返回一个对象
        后期通过对象get()获取返回值
        p.close()
        p.join()
    map
        简化了apply_async的操作
        直接拿到返回值的可迭代对象
        循环就可以获取返回值

使用进程池在多个进程之间通信 ,工作中很少用到IPC(队列)
第三方工具 :数据安全、redis、kafka、memcache

from multiprocessing import Pool,Queue,Pipe
def func(q):
    print(q)

if __name__ == '__main__':
    p = Pool()
    q =Pipe() #不能传队列作为子进程的参数,只能传管道
    p.apply(func,args=(q,))
    p.close()
    p.join()

import time
from multiprocessing import Pool,Queue,Pipe
def func(q):
    print(q)
    time.sleep(1)
    return q

if __name__ == '__main__':
    p = Pool()
    ret = p.map(func,range(20))
    for i in ret:    # 循环就可以获取返回值
        print('--> ',i)
  • 上下文管理
    • with ....
      一大段语句
回调函数
  • 子进程有大量的计算要去做,回调函数等待结果做简单处理

  • 当func执行完毕之后执行callback函数 ,重复做

  • func的返回值会作为callback的参数

  • 如果在主进程中等待进程池中所有任务都执行完毕后,再统一处理结果,则无需回调函数

      需要回调函数的场景:进程池中任何一个任务一旦处理完了,就立即告知主进程:我好了额,你可以处理我的结果了。主进程则调用一个函数去处理该结果,该函数即回调函数
    
      我们可以把耗时间(阻塞)的任务放到进程池中,然后指定回调函数(主进程负责执行),这样主进程在执行回调函数时就省去了I/O的过程,直接拿到的是任务的结果
    
import os
from multiprocessing import Pool
def func(i):
    print('第一个任务',os.getpid())  # 子进程pid
    return '*'*i,123

def call_back(res):   # func 的返回值(元祖的形式)作为参数传入call_back
    print('回调函数 :',os.getpid())   # 和主进程pid相同,是主进程实现的,回调函数
    print('res -->',res)

if __name__ == '__main__':
    p = Pool()
    print('主进程 : ',os.getpid())
    p.apply_async(func,args=(1,),callback=call_back)
    p.close()
    p.join()
  • 回调函数提取网页内容
url_lst = [
    'http://www.baidu.com',
    'http://www.sohu.com',
    'http://www.sogou.com',
    'http://www.4399.com',
    'http://www.cnblogs.com',
]
import re
from urllib.request import urlopen
from multiprocessing import Pool

def get_url(url):
    response = urlopen(url)
    ret = re.search('www\.(.*?)\.com',url)
    print('%s finished'%ret.group(1))
    return  ret.group(1),response.read()

def call(content):
    url,con = content
    with open(url+'.html','wb') as f:
        f.write(con)

if __name__ == '__main__':
    p = Pool()
    for url in url_lst:
        p.apply_async(get_url,args=(url,),callback=call)
    p.close()
    p.join()


url_lst = [
    'http://www.baidu.com',
    'http://www.sohu.com',
    'http://www.sogou.com',
    'http://www.4399.com',
    'http://www.cnblogs.com',
]
import re
from urllib.request import urlopen
from multiprocessing import Pool

def get_url(url):
    response = urlopen(url)
    ret = re.search('www\.(.*?)\.com',url)
    print('%s finished'%ret.group(1))
    return  ret.group(1),response.read()

def call(content):
    url,con = content
    with open(url+'.html','wb') as f:
        f.write(con)
# 回调
if __name__ == '__main__':
    p = Pool()
    for url in url_lst:
        ret = p.apply_async(get_url,args=(url,),callback=call)  # 效率高
    p.close()
    p.join()
# 非回调
if __name__ == '__main__':
    p = Pool()
    ret_l = []
    for url in url_lst:
        ret = p.apply_async(get_url,args=(url,))    # 效率低 非回调函数
        ret_l.append(ret)
    for ret in ret_l:
        call(ret.get())

你可能感兴趣的:(并发编程-进程)