Python - 多进程

创建进程

import os
from multiprocessing import Process


def func(args):
    # 要在新进程中执行的函数
    print("子进程1:"+args)
    print("子进程1id:" + str(os.getpid()))

class Myprocess(Process):
    def __init__(self,args):
        # 父类中也有init方法,重载需要调用父类的init方法
        super().__init__()
        # 初始化父类
        self.args = args
        # 传参

    def run(self):
        # run方法实现进程要执行的函数
        print("子进程2:"+str(self.args))
        print("子进程2id:" + str(self.pid))

if __name__ == '__main__':
    # 两种方法

    # 方法1
    p1 = Process(target=func, args=("参数1",))
    # args的参数为元组类型

    # 方法2
    p2 = Myprocess("参数2")
    # 创建进程
    # args的参数是元组类型

    print("父进程:****************")
    print("父进程id:" + str(os.getpid()))

    # 启动进程
    p1.start()
    p2.start()




结束进程

from multiprocessing import Process
import time
def func():
    for i in range(10):
        print(10-i)
        time.sleep(1)
if __name__ == '__main__':
    a = Process(target=func)
    a.start()
    print(a.is_alive())
    time.sleep(3)
    a.terminate() #发送结束进程的请求
    time.sleep(0.1) #等待系统响应
    print(a.is_alive())# 查看是否存活

join方法

作用:阻塞当前进程,直到调用join方法的那个进程执行完,再继续执行当前进程。
感知一个进程的结束

from multiprocessing import Process
import time

def func(args):
    print("子进程" + args + "启动")
    time.sleep(2)
    print("子进程" + args + "结束")
if __name__ == '__main__':
    p = Process(target=func,args=("1"))
    p.start()

    p.join()
    # 等待子进程结束,结束后继续执行下面的代码
    print("运行完了")

多进程之间的数据隔离

from multiprocessing import Process

def func():
    global a
    # 声明a是全局变量
    a = 10
    # 修改a的值
    print("子进程a:"+str(a))



if __name__ == '__main__':
    a = 1
    p = Process(target=func)
    p.start()
    p.join()
    # 等待子进程结束
    print("父进程a:"+str(a))
    # 输出父进程的a的值
    # a的值并没有因为func函数的修改而改变,这就说明了多进程之间的数据是相互隔离的

守护进程

守护进程:守护进程会随着主进程的代码执行完毕而结束
重点在于主进程的代码执行完毕,也就是说如果还有其他进程在执行,主进程不会结束,但是守护进程会结束

from multiprocessing import Process
import time
def func1():
    while True:
        time.sleep(0.5)
        print("守护进程存活")
def func2():
    print("进程2启动")
    time.sleep(8)
    print("进程2结束")
if __name__ == '__main__':
    p1 = Process(target=func1)
    p1.daemon = True
    p1.start()
    p2 = Process(target=func2)
    p2.start()
    for i in range(5):
        time.sleep(1)
        print("主进程存活")
    print("主进程代码执行完毕")

进程同步控制

进程锁

在日常生活中我们外出时一般都会遇到买票的情况,现在网络发达,大部分人都会选择在网上买票,而进程锁在这种情况下就会有很大作用

无进程锁情况下的买票程序

存有票数量的文件,json格式

{"ticket": 1}

源代码

import json
from multiprocessing import Process
import time



def buy_ticket():
    with open("ticket") as f:
        dic = json.load(f)
    if dic["ticket"] > 0:
        time.sleep(0.1) # 模拟网络延迟
        print("买到票了")
        dic["ticket"] -= 1
    else:
        print("没买到票")
    with open("ticket","w") as f:
        json.dump(dic,f)

if __name__ == '__main__':

    for i in range(10):
        Process(target=buy_ticket).start()

结果,每次运行结果都可能不同

买到票了
买到票了
没买到票
买到票了
买到票了
买到票了
买到票了
没买到票
没买到票
没买到票

总共只有一张票,程序却卖出了六张
这肯定不符合我们的要求
要实现有一张票就只能卖给一个人那就要用进程锁来实现

有进程锁的买票

进程锁就是相当于有一个房子,房子只有一个门,门上面的锁就只有一个钥匙,第一个来的进程可以拿到钥匙,并带着钥匙进入房间,完成任务后将钥匙归还,下一进程才能得到钥匙进入房间,这就避免了同时又多个进程同时执行而得到错误的结果

票数文件

{"ticket": 1}

源代码

import json
from multiprocessing import Process
import time
from multiprocessing import Lock



def buy_ticket(lock):
    lock.acquire() # 拿钥匙 进房子
    with open("ticket") as f:
        dic = json.load(f)
    if dic["ticket"] > 0:
        time.sleep(0.1) # 模拟网络延迟
        print("买到票了")
        dic["ticket"] -= 1
    else:
        print("没买到票")
    with open("ticket","w") as f:
        json.dump(dic,f)
    lock.release() # 出房子 还钥匙


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

信号量

假设一间房子只有4个座位,在房子门口放了4把钥匙,每次来人都会带着一把钥匙进房子,完成任务出门时会把钥匙放回门口,如果门口没有钥匙了就得等待房子里面的人完成任务出来,然后拿他归还的钥匙进入

from multiprocessing import Process
from multiprocessing import Semaphore
import time
import random
def func(s,sem):
    sem.acquire() # 拿钥匙 进房子
    print("%s进入房间"% s)
    time.sleep(random.randint(1,5))# 每个人进入房间的人随机在房间内待1-5秒
    print("%s走出房间"% s)
    sem.release()# 出房子 还钥匙
if __name__ == '__main__':
    sem = Semaphore(4)# 设置房间内只有4个座位
    for i in range(10):
        Process(target=func,args=(i,sem)).start()

事件

事件可以统一控制进程的阻塞和通行
红绿灯程序

from multiprocessing import Process,Event
import time
import random

def car(e,i):
    if not e.is_set():
        # 如果为红灯则等待
        print("车%s正在等待" % i)
        e.wait() # 等待e.is_set == True,否则一直等待
    print("车%s通过" % i)# 变为绿灯后直接通过

def light(e):
    while True:
        if e.is_set():
            # 如果为绿灯则变为红灯
            e.clear()# 将e.is_set设置为False
            print("红灯亮")
        else:
            # 如果红灯则变为绿灯
            e.set()# 将e.is_set设置为True
            print("绿灯亮")
        time.sleep(2)# 两秒切换一次红绿灯

if __name__ == '__main__':
    e = Event()# 创建事件

    Process(target=light,args=(e,)).start()
    # 启动红绿灯进程
    for i in range(20):
        # 生成二十个车的进程
        Process(target=car,args=(e,i)).start()
        # 启动进程
        time.sleep(random.random())
        # 随机等待0-1秒

进程通信

queue

from multiprocessing import Queue,Process

def f1(q):
    q.put("hello")

def f2(q):
    print(q.get())

if __name__ == '__main__':
    q = Queue()
    # 可以填写整数型参数,用来确定队列的长度
    Process(target=f1,args=(q,)).start()
    Process(target=f2,args=(q,)).start()

消费者生产者模型

queue实现

from multiprocessing import Queue,Process
import time
import random
def procuder(name,food,q):
    for i in range(10):
        time.sleep(random.randint(1,3))# 模拟生产所消耗的时间
        s = "%s生产了%s%s"%(name,food,i)
        print(s)
        q.put(s)


def consumer(name,q):
    while True:
        time.sleep(random.randint(1,3))# 模拟消费所消耗的时间
        f = q.get()
        if f is None:break # 判断队列是否已经没有需要处理的数据
        print("%s消耗了%s"%(name,f))

if __name__ == '__main__':
    q = Queue()
    p1 = Process(target=procuder,args=("A","包子",q))
    p1.start()
    p2 = Process(target=procuder,args=("B","面条",q))
    p2.start()

    Process(target=consumer,args=("C",q)).start()
    Process(target=consumer,args=("D",q)).start()

    p1.join()
    p2.join()
    # 两个生产者进程结束表明已经没有需要处理的数据
    q.put(None)
    q.put(None)
    # 队列的数据只能被一个进程获取
    # 要保证每一个消费者进程都结束
    # 所以每有一个消费者进程就要向队列中添加一个None

joinablequeue 实现

使用 queue需要给每个进程添加一个结束标志
使用joinablequeue可以避免这个问题

from multiprocessing import JoinableQueue,Process
import time
import random
def procuder(name,food,q):
    for i in range(10):
        time.sleep(random.randint(1,3))# 模拟生产所消耗的时间
        s = "%s生产了%s%s"%(name,food,i)
        print(s)
        q.put(s)
    q.join()
    # 生产已经结束
    # 阻塞,直到感知到消费者把所有数据都处理完
    # 也就是感知所有的记号都被清除后解除阻塞


def consumer(name,q):
    while True:
        time.sleep(random.randint(1,3))# 模拟消费所消耗的时间
        f = q.get()
        print("%s消耗了%s"%(name,f))
        q.task_done()#抹除记号

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

    p1 = Process(target=procuder,args=("A","包子",q))
    p2 = Process(target=procuder,args=("B","面条",q))
    p1.start()
    p2.start()

    c1 = Process(target=consumer,args=("C",q))
    c2 = Process(target=consumer,args=("D",q))

    # 设置守护进程
    c1.daemon = True
    c2.daemon = True
    c1.start()
    c2.start()

    # 感知生产者函数的结束
    p1.join()
    p2.join()

    '''
    joinablequeue 每次put都会留下一个记号
    每次get需要手动抹除一个记号
    
    生产者的join方法保证了所有数据都被处理完了才会结束生产者进程
    主函数中的生产者进程的join保证了生产者进程结束后主函数代码才执行完毕
    消费者进程设置为守护进程会随着主函数代码执行完毕而结束
    
    '''

管道

from multiprocessing import Pipe,Process
def func(conn):
    print(conn.recv())#接收从管道端口2发送到端口1的数据

if __name__ == '__main__':
    conn1,conn2 = Pipe() # 创建管道
    Process(target=func,args=(conn1,)).start()# 创建进程并将管道端口1传入
    conn2.send("123456")# 从管道端口2传入数据
    # 管道是双向通信的组件

进程池

map

  • 异步,自带close和join方法
  • 返回值是所有结果的列表
  • map会等待所有结果都计算完成后一块返回
from multiprocessing import Pool,Process
import time


def func(n):
    for i in range(10):
        print(n+1)


if __name__ == '__main__':
    p = Pool(5)
    # 参数可以为空,为空默认为cpu数量或1
    # 创建一个进程池,里面有5个进程
    start = time.time()
    # 启动时间
    p.map(func,range(100))
    # p.map(方法,参数(可迭代))
    # 创建100个任务
    t1 = time.time() - start

    start = time.time()
    p_list = []
    for i in range(100):
        p1 = Process(target=func, args=(i,))
        p_list.append(p1)
        p1.start()
    for i in p_list:i.join()
    t2 = time.time() - start
    print("进程池:"+str(t1), "普通进程:"+str(t2))



apply

  • 同步:只有func执行完成后才会向后执行
  • 返回值就是func的return
from multiprocessing import Pool
import os
import time


def func(n):
    print("start:%s" % n, os.getpid())
    time.sleep(1)
    print("end:%s" % n, os.getpid())


if __name__ == '__main__':
    p = Pool()
    for i in range(10):
        p.apply(func=func,args=(i,))
    p.close()
    # 进程池停止接收任务
    p.join()
    # 感知进程池任务结束

apply_async

  • 异步
from multiprocessing import Pool
import os
import time


def func(n):
    print("start:%s" % n, os.getpid())
    time.sleep(1)
    print("end:%s" % n, os.getpid())


if __name__ == '__main__':
    p = Pool()
    for i in range(10):
        p.apply_async(func=func,args=(i,))
    p.close()
    # 进程池停止接收任务
    p.join()
    # 感知进程池任务结束

进程池的返回值

from multiprocessing import Pool
import time


def func(n):
    time.sleep(0.5)
    return n*n


if __name__ == '__main__':
    p = Pool(5)

    print("map:")
    # map的返回值是等待所有结果执行完毕统一返回
    ret = p.map(func,range(10))
    print(ret)

    print("apply_async:")
    # apply_async的返回值是进程池内的一批任务执行完毕就返回一批的结果
    ret_lst = []
    for i in range(10):
        ret = p.apply_async(func,args=(i,))
        # 创建任务
        ret_lst.append(ret)
        # 将任务的结果存入结果列表
    for i in ret_lst:print(i.get())
    # 打印结果

    # 如果apply_async之后直接接ret.get()则会将进程池转换为同步进程池
    # 因为ret.get会阻塞进程等待返回值,这样就相当于把进程池变成了同步进程池

Windows中需要注意的点

错误1

当出现这种错误的时候说明你的进程调用没有判断是否在__name__ == __main__的环境下运行

Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "C:\py3.7\lib\multiprocessing\spawn.py", line 105, in spawn_main
    exitcode = _main(fd)
  File "C:\py3.7\lib\multiprocessing\spawn.py", line 114, in _main
    prepare(preparation_data)
  File "C:\py3.7\lib\multiprocessing\spawn.py", line 225, in prepare
    _fixup_main_from_path(data['init_main_from_path'])
  File "C:\py3.7\lib\multiprocessing\spawn.py", line 277, in _fixup_main_from_path
    run_name="__mp_main__")
  File "C:\py3.7\lib\runpy.py", line 263, in run_path
    pkg_name=pkg_name, script_name=fname)
  File "C:\py3.7\lib\runpy.py", line 96, in _run_module_code
    mod_name, mod_spec, pkg_name, script_name)
  File "C:\py3.7\lib\runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "F:\编程语言区\python\源码\学习\进程控制\多进程\杀死进程.py", line 10, in <module>
    a.start()
  File "C:\py3.7\lib\multiprocessing\process.py", line 112, in start
    self._popen = self._Popen(self)
  File "C:\py3.7\lib\multiprocessing\context.py", line 223, in _Popen
    return _default_context.get_context().Process._Popen(process_obj)
  File "C:\py3.7\lib\multiprocessing\context.py", line 322, in _Popen
    return Popen(process_obj)
  File "C:\py3.7\lib\multiprocessing\popen_spawn_win32.py", line 33, in __init__
    prep_data = spawn.get_preparation_data(process_obj._name)
  File "C:\py3.7\lib\multiprocessing\spawn.py", line 143, in get_preparation_data
    _check_not_importing_main()
  File "C:\py3.7\lib\multiprocessing\spawn.py", line 136, in _check_not_importing_main
    is not going to be frozen to produce an executable.''')
RuntimeError: 
        An attempt has been made to start a new process before the
        current process has finished its bootstrapping phase.

        This probably means that you are not using fork to start your
        child processes and you have forgotten to use the proper idiom
        in the main module:

            if __name__ == '__main__':
                freeze_support()
                ...

        The "freeze_support()" line can be omitted if the program
        is not going to be frozen to produce an executable.

解决方法:
将进程的调用放到如下代码中执行即可

if __name__ == '__main__':
    a = Process(target=func)
    a.start()
posted @ 2019-09-23 15:15  长江尾  阅读(281)  评论(0编辑  收藏  举报