多任务-进程

文章目录

    • 进程
      • 进程以及状态
      • 使用Process完成多进程
        • 2个while循环一起执行
        • 多进程实现多任务
        • 进程 PID
      • 进程与线程的区别
      • 进程间的通信 - Queue
        • 多进程之间通过Queue实现数据共享
      • 进程池 Pool
        • 进程池 Pool - 概述
        • 进程池的创建
        • RuntimeError异常的解决方法
      • 案例:文件夹copy器_多进程版

进程

进程以及状态

进程

==程序:==例如xxx.py,是一个静态的
==进程:==一个程序运行起来后,代码+用到的资源称之为进程,它是操作系统分配资源的基本单元。不仅可以通过线程完成多任务,进程也是可以的。

进程的状态

工作中,任务数往往大于CPU的核数,即一定有一些任务正在执行,而另外一些任务在等待CPU进行执行,因此导致了有了不同的状态。

多任务-进程_第1张图片

  • 就绪态:运行的条件都已经准备好,正在等待CPU执行。
  • 执行态:CPU正在执行其功能。
  • 等待态:等待某些条件满足,例如一个程序sleep了,此时就出于等待态。

使用Process完成多进程

进程的创建-multiprocessing

multiprocessing模块就是跨平台版本的多进程模块,提供了一个Process类来代表一个进程对象,这个对象可以理解为是一个独立的进程,可以执行另外的事情。

2个while循环一起执行

from multiprocessing import Process
import time


def run_proc():
	"""子进程要执行的代码"""
	while Trueprint("-----2-----")


if __name__ == "__main__"
	p = Process(target=run_proc)
	p.start()
	while True:
		print("-----1-----")
		time.sleep(1)

说明

  • 创建子进程时,只需要传入一个执行函数函数的参数,创建一个Process实例,用 start() 方法启动。

多进程实现多任务


import threading
import time
import multiprocessing


def test1():
    while True:
        print("-----1-----")
        time.sleep(1)


def test2():
    while True:
        print("-----2-----")
        time.sleep(1)


def main():

    # t1 = threading.Thread(target=test1)
    # t2 = threading.Thread(target=test1)
    # t1.start()
    # t2.start()


    p1 = multiprocessing.Process(target=test1)
    p2 = multiprocessing.Process(target=test2)
    p1.start()
    p2.start()


if __name__ == "__main__":
    main()

附:线程能实现多任务,进程也能实现,但是进程耗费的资源很大。一个系统上运行的进程数越多,其实占用的资源就越大,运行的效率就越低。所以进程数不是越多越好!

进程 PID


from multiprocessing import Process
import os
import time


def run_proc():
    """子进程要执行的代码"""
    print("子进程运行中,PID = %d..." % os.getpid())  # os.getpid 获取当前进程的PID号
    time.sleep(1)
    print("子进程将要结束...")


if __name__ == "__main__":
    print("父进程运行中,PID = %d..." % os.getpid())  # os.getpid 获取当前进程的PID号
    p = Process(target=run_proc)
    p.start()

进程与线程的区别

功能

  • 进程:能够完成多任务,比如在一台电脑上能够同时运行多个QQ
  • 线程:能够完成多任务,比如一个QQ中的多个聊天窗口

定义的不同

  • 进程时系统进行资源分配和调度的一个单位。
  • 线程时进程的一个实体,是CPU调度和分配的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可以与同属一个进程的其他线程共享进程所拥有的全部资源。

区别

  • 一个程序至少有一个线程,一个线程至少有一个进程。
  • 线程的划分尺度小于进程(资源比进程少),使得多线程程序的并发性高。
  • 进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
  • 线程不能够独立运行,必须依存在进程中。
  • 可以将进程理解为工厂中的一条流水线,而其中的线程就是这个流水线上的工人。

优缺点

线程与进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程则相反!

进程间的通信 - Queue

  • Process之间有时需要通信,操作系统提供了很多机制来实现进程间的通信。

Queue的使用

可以使用multiprocessing模块的Queue实现多进程之间的数据传递,Queue本身就是一个消息队列程序,首先用一个小实例来演示一下Queue的工作原理。

示例代码如下:

import multiprocessing


q = multiprocessing.Queue(3)  # 初始化一个Queue对象,最多可以接收3条 put 消息
q.put("消息1")
q.put("消息2")
print(q.full())  # False
q.put("消息3")
print(q.full())  # True


# 因为消息队列已满,下面的try都会抛出异常,第一个try会等待2秒后再抛出异常,第二个try会立刻抛出异常


try:
    q.put("消息4", True, 2)
except:
    print("消息队列已满,现有消息数量:%s" % q.qsize())


try:
    q.put_nowait("消息4")
except:
    print("消息队列已满,现有消息数量:%s" % q.qsize())


# 推荐的方式,先判断消息队列是否已满,再写入
if not q.full():
    q.put_nowait("消息4")


# 读取消息时,先判断消息队列是否为空,再读取
if not q.empty():
    for i in range(q.qsize()):
        print(q.get_nowait())


多进程之间通过Queue实现数据共享

import multiprocessing


def download_from_web(q):
    """下载数据"""
    # 模拟从网上下载的数据
    data = [11, 22, 33, 44]

    # 向队列中写入数据
    for temp in data:
        q.put(temp)

    print("---下载器已经完成了数据并且存入到队列中---")


def analysis_data(q):
    """数据处理分析"""
    waitting_analysis_data = list()
    # 从队列中获取数据
    while True:
        data = q.get()
        waitting_analysis_data.append(data)

        if q.empty():
            break

    # 模拟数据处理
    print(waitting_analysis_data)


def main():
    # 1.创建一个队列
    q = multiprocessing.Queue()

    # 2.创建多个进程,将队列的引用当作实参进行传递到里面
    p1 = multiprocessing.Process(target=download_from_web, args=(q,))
    p2 = multiprocessing.Process(target=analysis_data, args=(q,))
    p1.start()
    p2.start()


if __name__ == "__main__":
    main()

进程池 Pool

进程池 Pool - 概述

当需要创建的子进程数量不多时,可以直接利用multiprocessing的中Process动态生成多个进程,但如果是上百甚至上千个目标,手动的去创建进程的工作量巨大,此时就可以用到multiprocessing模块提供的Pool方法。

初始化Pool时,可以指定一个最大进程数,当有新的请求提交到Pool中时,如果池还没有满,那么就会创建一个新的进程来执行该请求;但如果池中的进程数已经达到指定的最大值,那么该请求就会等待,直到池中有进程结束,才会用之前的进程来执行新的任务,请看下面的实例

进程池的创建

代码示例

from multiprocessing import Pool
import os
import time
import random


def worker(msg):
    t_start = time.time()
    print("[%s]开始执行,进程号为[%d]" % (msg, os.getpid()))
    # random.random() 随机生成0~1之间的浮点数
    time.sleep(random.random()*2)
    t_stop = time.time()
    print(msg, "执行完毕,耗时%0.2f" % (t_stop - t_start))


po = Pool(3)  # 定义一个进程池,最大进程数:3
for i in range(0, 10):
    # Pool().apply_async(要调用的目标, (传递给目标的参数元组,))
    # 每次循环将会用空闲出来的子进程去调用目标
    po.apply_async(worker, (i,))


print("-----start-----")
po.close()  # 关闭进程池,关闭后po不再接收新的语句
po.join()  # 等待po中所有子进程执行完成,必须放在close语句之后
print("-----end-----")

注意:
上文代码在Linux、Mac上是可以正常执行的,但是如果是在Windows系统执行就会抛出RuntimeError异常
经过网络搜集相关信息,得出一个初步的判断,具体对不对我也不知如何验证。这里姑且借前人之言说一下。

造成该异常是因为 Windows系统下在Pycharm 运行 multiprocessing 造成的进程阻塞产生的。

异常信息如下

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.

RuntimeError异常的解决方法

设置函数入口

from multiprocessing import Pool
import os
import time
import random


def worker(msg):
    t_start = time.time()
    print("[%s]开始执行,进程号为[%d]" % (msg, os.getpid()))
    # random.random() 随机生成0~1之间的浮点数
    time.sleep(random.random()*2)
    t_stop = time.time()
    print(msg, "执行完毕,耗时%0.2f" % (t_stop - t_start))


if __name__ == "__main__":

    po = Pool()  # 定义一个进程池,最大进程数:3
    for i in range(0, 10):
        # Pool().apply_async(要调用的目标, (传递给目标的参数元组,))
        # 每次循环将会用空闲出来的子进程去调用目标
        po.apply_async(worker, (i,))

    print("-----start-----")
    po.close()  # 关闭进程池,关闭后po不再接收新的语句
    po.join()  # 等待po中所有子进程执行完成,必须放在close语句之后
    print("-----end-----")

抛出RuntimeError异常

from multiprocessing import Pool
import os
import time
import random


try:

    def worker(msg):
        t_start = time.time()
        print("[%s]开始执行,进程号为[%d]" % (msg, os.getpid()))
        # random.random() 随机生成0~1之间的浮点数
        time.sleep(random.random()*2)
        t_stop = time.time()
        print(msg, "执行完毕,耗时%0.2f" % (t_stop - t_start))


    # if __name__ == "__main__":

    po = Pool()  # 定义一个进程池,最大进程数:3
    for i in range(0, 10):
        # Pool().apply_async(要调用的目标, (传递给目标的参数元组,))
        # 每次循环将会用空闲出来的子进程去调用目标
        po.apply_async(worker, (i,))

    print("-----start-----")
    po.close()  # 关闭进程池,关闭后po不再接收新的语句
    po.join()  # 等待po中所有子进程执行完成,必须放在close语句之后
    print("-----end-----")

except RuntimeError:
    print("multiprocessing 在 'Windows'系统下的[RuntimeError]异常,请忽略。")

案例:文件夹copy器_多进程版

import os
import multiprocessing


def copy_file(queue, file_name, old_folder_name, new_folder_name):
    """完成文件的复制"""
    # print("=====>模拟copy文件:从【%s】----->到【%s】,文件名:【%s】" % (old_folder_name, new_folder_name, file_name))
    old_f = open(old_folder_name + "/" + file_name, "rb")  # 拼个路径
    content = old_f.read()
    old_f.close()

    new_f = open(new_folder_name + "/" + file_name, "wb")
    new_f.write(content)
    new_f.close()


    # 如果copy完了文件,那么就向队列中写入一个消息,表示已经完成
    queue.put(file_name)


def main():
    # 1.获取用户要copy的文件夹的名字
    old_folder_name = input("请输入要copy的文件夹的名字:")

    # 2.创建一个新的文件夹
    try:
        new_folder_name = old_folder_name + "[复件]"
        os.mkdir(new_folder_name)
    except:
        pass

    # 3.获取文件夹的所有的待copy的文件夹名字  listdir()
    file_names = os.listdir(old_folder_name)
    # print(file_names)

    # 4.创建进程池
    po = multiprocessing.Pool(5)

    # 5.创建一个队列
    queue = multiprocessing.Manager().Queue()

    # 6.向进程池中添加 copy 文件的任务
    for file_name in file_names:
        po.apply_async(copy_file, args=(queue, file_name, old_folder_name, new_folder_name))

    po.close()
    # po.join()
    all_file_num = len(file_names)  # 测一下所有的文件个数
    copy_ok_num = 0
    while True:
        file_name = queue.get()
        # print("已经完成COPY:%s" % file_name)
        copy_ok_num += 1
        print("\rCOPY的进度为:%.2f %% " % (copy_ok_num*100 / all_file_num), end="")

        if copy_ok_num >= all_file_num:
            break

    print()


if __name__ == "__main__":
    main()



你可能感兴趣的:(python实战,Python学习之路)