Python 多进程

1. 进程

# 这段代码在pycharm中可能不出结果,可以放到cmd中运行
import threading
import multiprocessing
import time


def subTest():
    print('This is in Process:%s thread: %s' % (multiprocessing.current_process(),threading.get_ident()))

    
def test():
    time.sleep(2)
    print('This is process: %s' % multiprocessing.current_process())  # 打印当前进程
    threading.Thread(target=subTest,).start()   # 在进程中启动一个线程

    
if __name__ == '__main__':
    for i in range(10):
        p = multiprocessing.Process(target=test,)  # 生成进程
        p.start()

2. 父进程

每个子进程都会获得父进程数据空间、堆和栈(简而言之,子进程会将父进程的所有数据,都复制一份。因为多线程是不复制的,多线程共享进程中的数据)

# 这段代码只有在命令行下才能正常打印出结果,在pycharm中不出结果
import threading
import multiprocessing
import time
import os


def subTest():
    print(f'ppid:{os.getppid()} , pid:{os.getpid()}')  # 打印父进程id,和当前进程id

    
def test():
    time.sleep(2)
    print('This is process: %s' % multiprocessing.current_process())  # 打印当前进程
    threading.Thread(target=subTest,).start()   # 在进程中启动一个线程

    
if __name__ == '__main__':
    subTest()
    for i in range(2):
        p = multiprocessing.Process(target=test,)  # 生成进程
        p.start()

结果:

ppid:4276 , pid:9356                       # ppid:4267是系统终端的进程,这里是cmd.exe,由系统终端创建出了pid:9356是当前的进程
This is process: <Process name='Process-2' parent=9356 started>  # 子进程,由父进程9356创建而来
This is process: <Process name='Process-1' parent=9356 started>  # 子进程,由父进程9356创建而来
ppid:9356 , pid:9648   # 子进程的线程打印的信息,可以看到父进程的id和当前进程的id
ppid:9356 , pid:5288

3. 自定义进程类

# python3.7以上版本可能在pycharm中不能正常打印结果,可以放到cmd运行
import threading
import multiprocessing
import time
import os


class MyProcess(multiprocessing.Process):  # 继承进程类
    def subTest(self):
        print(f'ppid:{os.getppid()} , pid:{os.getpid()},{multiprocessing.current_process()}')  # 此线程中打印进程的父进程和当前进程

    def test(self):
        time.sleep(2)
        print('This is process: %s' % multiprocessing.current_process())  # 打印当前进程
        threading.Thread(target=self.subTest,).start()   # 在进程中启动一个线程

    def run(self):  # 重写父类的 run 方法,需要执行的进程写在 run()函数里面
        self.test()

        
if __name__ == '__main__':
    for i in range(2):
        p = MyProcess()  # 实例化
        p.start()        # 启动进程活动

4. 进程间通信:queue

使用进程queue,可以实现多个进程间的数据通信。原理应该是每个进程对queue的入队和出队都进行序列化,放到一个公共区域,其他进程通过反序列化这些数据,实现数据的同步。

# python3.7以上版本可能在pycharm中不能正常打印结果,可以放到cmd运行
import threading
import multiprocessing
import time
import os
from multiprocessing import Queue  # 导入进程中的queue


class MyProcess(multiprocessing.Process):  # 继承进程类
    def __init__(self,qq):
        super().__init__()
        self.qq = qq
    def subTest(self):
        print(f'ppid:{os.getppid()} , pid:{os.getpid()},{multiprocessing.current_process()}')  # 此线程中打印进程的父进程和当前进程

    def test(self):
        time.sleep(2)
        print('Process qq.get: %s' % self.qq.get())
        print('This is process: %s' % multiprocessing.current_process())  # 打印当前进程
        # threading.Thread(target=self.subTest,).start()   # 在进程中启动一个线程

    def run(self):  # 重写父类的 run 方法,需要执行的进程写在 run()函数里面
        self.test()

        
if __name__ == '__main__':
    q = Queue()  # 实例化进程Queue
    q.put(1)     # 入队一个数据
    q.put(2)
    q.put(3)
    q.put(3)

    p0 = MyProcess(q)
    p0.subTest()
    p0.test()
    for i in range(2):   # 开启两个进程:
        p = MyProcess(q)  # 实例化
        p.start()        # 启动进程活动

5. 进程 Pipe 管道

通过Pipe,进程间可以使用 send、recv 来发送和接收数据,实现进程间的通信。其中send和recv是一一对应的,即如果你 send 三次,只 recv 一次,那么 recv 得到的数据是第一次 send 时发出的数据,后两次 send 的数据会被放进缓存,等待 recv 。相反如果 recv 次数大于 send 次数,多出来的 recv 会阻塞,直到有相应的 send 数据过来。

from multiprocessing import Pipe,Process
import multiprocessing


def func(tail):
    tail.send(f'This is {multiprocessing.current_process()}')
    print(tail.recv())

    
if __name__ == "__main__":
    head,tail = Pipe()   # 实例化一个管道,返回两个对象,代表管道的两端
    for i in range(5):   # 创建5个进程
        p = Process(target=func,args=(tail,))  # 将管道的一头 tail 传递给进程,可以通过tail来进行进程间的通信
        p.start()
    # 发送6条数据
    head.send(f'From {multiprocessing.current_process()}, 1')
    head.send(f'From {multiprocessing.current_process()}, 2')
    head.send(f'From {multiprocessing.current_process()}, 3')
    head.send(f'From {multiprocessing.current_process()}, 4')
    head.send(f'From {multiprocessing.current_process()}, 5')
    head.send(f'From {multiprocessing.current_process()}, 6')
    
    # 接收5个进程发来的数据
    print(head.recv())
    print(head.recv())
    print(head.recv())
    print(head.recv())
    print(head.recv())

结果:

This is <Process name='Process-1' parent=9248 started>
From <_MainProcess name='MainProcess' parent=None started>, 1
This is <Process name='Process-2' parent=9248 started>
From <_MainProcess name='MainProcess' parent=None started>, 2
This is <Process name='Process-4' parent=9248 started>
From <_MainProcess name='MainProcess' parent=None started>, 3
This is <Process name='Process-3' parent=9248 started>
From <_MainProcess name='MainProcess' parent=None started>, 4
This is <Process name='Process-5' parent=9248 started>
From <_MainProcess name='MainProcess' parent=None started>, 5

请按任意键继续. . .

6. Manager

Manager封装了一些常用的数据类型,如 list, dict, Namespace, Lock, RLock, Semaphore,可以让多进程共享这些数据。

import multiprocessing
from multiprocessing import Manager


def func(ls,i):
    '''给函数传递一个list类型的值,每次增加一个值'''
    ls.append(i)

    
if __name__ == "__main__":
    manager = Manager()  # 实例化Manager
    ls = manager.list()  # 调用list()方法,声明一个list对象
    pls = []             # 声明一个list对象,用来存放 进程实例

    for i in range(10):  # 循环10次,创建10个进程
        p = multiprocessing.Process(target=func,args=(ls,i))  # 实例化进程
        p.start()                          # 启动进程
        pls.append(p)                      # 将进程实例添加到pls中

    for p in pls:  # 遍历 进程实例,执行join方法,等待进程结束
        p.join()

    print(ls)    # 查看ls的值

结果:可以看出,多个进程共享了一个 list 类型的数据,每个进程添加的数据,都是添加到一个列表里的。

[0, 8, 7, 5, 2, 1, 9, 4, 3, 6]

一个使用了 Value 来自定义数据类型的例子。这个例子来源于:https://blog.csdn.net/yldmkx/article/details/115948722

import ctypes
import multiprocessing
import time


def process_write(int_data, str_data, list_data, dict_data):
	i = 1
	while True:
		int_data.value = i
		str_data.value = 'str' + str(i)
		list_data[0] = i
		dict_data['dict0'] = i
		print("Write index", i)
		i += 1


def process_read(int_data, str_data, list_data, dict_data):
	while True:
		print("Read data", int_data, str_data, list_data, dict_data)


if __name__ == "__main__":
	int_data = multiprocessing.Manager().Value(ctypes.c_int, 0)  # 定义了一个整数
	str_data = multiprocessing.Manager().Value(ctypes.c_char_p, 'str0')  # 字符串
	list_data = multiprocessing.Manager().list()  # 列表
	list_data.append(0)
	dict_data = multiprocessing.Manager().dict()  # 字典
	dict_data['dict0'] = 0

	p1 = multiprocessing.Process(target=process_write, args=(int_data, str_data, list_data, dict_data))
	p2 = multiprocessing.Process(target=process_read, args=(int_data, str_data, list_data, dict_data))

	p1.start()
	p2.start()

	time.sleep(1)
    
	p1.terminate()
	p2.terminate()

7. 进程锁 Lock

当多个进程对同一个资源进行修改编辑时,为了让这些修改有序进行,可以让此资源一次只能被一个进程占用,其他进程等待此资源被释放后才能占用

import multiprocessing
import json
import time

# 将dict1通过json 序列化到磁盘 json文件中
dict1 = {'ticket':2}  # 默认车票剩余2张
json.dump(dict1,open('json','w'))
time.sleep(2)


def buy(name,lock):
    lock.acquire()                              # 加锁
    ticket = json.load(open('json','r'))        #反序列化,获取ticket信息
    if ticket['ticket'] > 0:         # 当车票数量大于0,就减去一张,代表被当前用户买走了。
        ticket['ticket'] -= 1
        print(f'{name} buy a ticket.')
        json.dump(ticket,open('json','w'))     # 重新序列化车票
    else:
        print(f'No ticket left!')
    lock.release()                             # 释放锁

    
if __name__ == '__main__':
    lock = multiprocessing.Lock()  # 实例化一个锁

    pList = []
    for i in range(5):  # 准备生成5个进程,代表5个用户来买票
        p = multiprocessing.Process(target=buy,args=(i+1,lock))  # 将锁作为参数传递给函数
        p.start()
        pList.append(p)

    for i in pList:  # 等待进程执行完毕
        i.join()

不能把上面的json直接换成 字典,否则 Lock 是不起作用的,例子如下。因为 字典 是主进程中的放在内存的数据,每个子进程启动时会复制父进程的内容,相当于每个子进程独享一个 字典 ,这样它们之间的更改不会共享,如果想要共享这些数据,可以使用上面的Manager。

import multiprocessing
import time

# 直接使用字典
dict1 = {'ticket':2}


def buy(name,lock):
    lock.acquire()
    if dict1['ticket'] > 0:
        dict1['ticket'] -= 1
        print(f'{name} Buy a ticket.',dict1)
    else:
        print('No ticket left.')
    lock.release()

    
if __name__ == '__main__':
    lock = multiprocessing.Lock()

    pList = []
    for i in range(5):  # 准备生成5个进程,代表5个用户来买票
        p = multiprocessing.Process(target=buy,args=(i+1,lock))  # 将锁作为参数传递给函数
        p.start()
        pList.append(p)
        p.join()

结果:每个人都买到票了:

1 Buy a ticket. {'ticket': 1}
2 Buy a ticket. {'ticket': 1}
3 Buy a ticket. {'ticket': 1}
5 Buy a ticket. {'ticket': 1}
4 Buy a ticket. {'ticket': 1}

8. 进程池

import multiprocessing
from time import sleep
import os
from multiprocessing import freeze_support  # windows上要加,但是依然好像没啥用


def run(i):
    sleep(2)
    print('%s ,PID is: ' %i,os.getpid(),os.getppid())

    
def back(self):
    print('This is callback.',os.getpid())

    
if __name__ == '__main__':
    freeze_support()

    # 每次最多运行5个进程
    pool = multiprocessing.Pool(processes=5)
    for i in range(10):
        # callback回调函数
        pool.apply_async(func=run,args=(i,),callback=back)  # 异步执行,并行
        # pool.apply(func=run,args=(i,))        # 同步执行,串行

    pool.close()  # 关闭连接池
    pool.join()   # 等待的每个子进程执行完毕,不写的话,主进程结束后就直接退出了。

    print('End.')
posted @ 2019-12-03 22:06  wztshine  阅读(373)  评论(0编辑  收藏  举报