python高级 6 进程

进程、进程的使用、进程注意点、进程间通信-Queue、进程池Pool、进程与线程对比、文件夹拷贝器-多任务

1.进程介绍

多进程本质是创建一个进程默认创建一个线程,任务本身是由线程完成的。多进程可以处理多任务,但是每创建一个进程都要向系统索要运行资源,相比线程来说资源占用比较多成本比较大

<1>进程的概念

进程:通俗理解一个运行的程序或者软件,进程是操作系统资源分配的基本单位。

注意:一个程序至少有一个进程,一个进程至少有一个线程,多进程可以完成多任务.

<2>进程的状态

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

  就绪态:运行的条件都已经满足,正在等待cpu执行

  执行态:cpu正在执行其功能

  等待态:等待某些条件满足,例如一个程序sleep了,此时就处于等待态

小结:

一个进程默认有一个线程,进程里面可以创建线程,线程是依附在进程里面的,没有进程就没有线程。

2.进程的使用

<1>多进程完成多任务

  导入进程模块

import multiprocessing

<2>Process进程类的语法结构如下:

Process([group [, target [, name [, args [, kwargs]]]]])

  group:指定进程组,目前只能使用None

  target:执行的目标任务名

  name:进程名字

  args:以元组方式给执行任务传参

  kwargs:以字典方式给执行任务传参

Process创建的实例对象的常用方法:

  start():启动子进程实例(创建子进程)

  join([timeout]):是否等待子进程执行结束,或等待多少秒

  terminate():不管任务是否完成,立即终止子进程

Process创建的实例对象的常用属性:

  name:当前进程的别名,默认为Process-N,N为从1开始递增的整数

  pid:当前进程的pid(进程号)

<3>多进程完成多任务代码

import multiprocessing

import time

def run_proc():

    """子进程要执行的代码"""

    while True:

        print("----2----")

        time.sleep(1)

if __name__=='__main__':

    # 创建子进程

    sub_process = multiprocessing.Process(target=run_proc)

    # 启动子进程

    sub_process.start()

    while True:

        print("----1----")

        time.sleep(1)

<4>获取当前进程、当前进程pid、父进程pid(paternal pid)、杀死对应的进程

  获取当前进程

      multiprocessing.current_process()

  获取当前进程的编号

      multiprocessing.current_process().pid

      os.getpid()

  获取父进程的编号

      os.getppid()

  根据进程编号杀死对应的进程

      os.kill(os.getpid(), 9)

import multiprocessing

import time

import os

def work():

    # 查看当前进程

    current_process = multiprocessing.current_process()

    print("work:", current_process)

    # 获取当前进程的编号

    print("work进程编号:", current_process.pid, os.getpid())

    # 获取父进程的编号

    print("work父进程的编号:", os.getppid())

    for i in range(10):

        print("工作中....")

        time.sleep(0.2)

        # 扩展: 根据进程编号杀死对应的进程

        os.kill(os.getpid(), 9)

if __name__ == '__main__':

    # 查看当前进程

    current_process = multiprocessing.current_process()

    print("main:", current_process)

    # 获取当前进程的编号

    print("main进程的编号:", current_process.pid)

    # 创建子进程

    sub_process = multiprocessing.Process(target=work)

    # 启动进程

    sub_process.start()

    # 主进程执行打印信息操作

    for i in range(20):

        print("我在主进程中执行...")

        time.sleep(0.2)

<5>给子进程指定的函数传递参数

import multiprocessing

# 显示人员信息

def show_info(name, age):

    print(name, age)

if __name__ == '__main__':

    # 创建子进程

    # 1. group:进程组,目前必须使用None,一般不用设置

    # 2. target:执行目标函数

    # 3. name: 进程名称

    # 4. args: 以元组方式给函数传参

    # 5. kwargs: 以字典方式给函数传参

    sub_process = multiprocessing.Process(target=show_info, name="myprocess", args=("古力娜扎", 18))

    # 启动进程

    sub_process.start()

    # sub_process = multiprocessing.Process(target=show_info, name="myprocess", kwargs={"name": "貂蝉", "age": 20})

    #

    # # 启动进程

    # sub_process.start()

3.进程注意点

<1>进程之间不共享全局变量

import multiprocessing

import time

# 定义全局变量

my_list = list()

# 写入数据

def write_data():

    for i in range(5):

        my_list.append(i)

        time.sleep(0.2)

    print("write_data:", my_list)

# 读取数据

def read_data():

    print(my_list)

if __name__ == '__main__':

    # 创建写入数据的进程

    write_process = multiprocessing.Process(target=write_data)

    read_process = multiprocessing.Process(target=read_data)

    write_process.start()

    # 主进程等待写入进程执行完成以后代码 再继续往下执行

    write_process.join()

    read_process.start()

执行结果:

write_data: [0, 1, 2, 3, 4]

read_data: []

注意:

创建子进程其实是对主进程(除了main方法程序入口以外的,Windows如果创建子进程不用main程序入口会无限循环创建进程导致程序报错)所有资源进行拷贝,进程之间相互独立,访问的全局变量不是同一个,所以进程之间不共享全局变量

多进程之间可能出现相同名字的全局变量,但是不是同一个全局变量,是不同的全局变量,只不过名字相同而已

<2>主进程会等待所有的子进程执行完成程序再退出

import multiprocessing

import time

# 测试子进程是否执行完成以后主进程才能退出

def work():

    for i in range(10):

        print("工作中...")

        time.sleep(0.2)

if __name__ == '__main__':

    # 创建子进程

    work_process = multiprocessing.Process(target=work)

    work_process.start()

    # 让主进程等待1秒钟

    time.sleep(1)

    print("主进程执行完成了啦")

总结: 主进程会等待所有的子进程执行完成以后程序再退出

<3>销毁子进程的代码

设置守护主进程,主进程退出后子进程直接销毁,不再执行子进程中的代码

子进程名.daemon = True

让子进程直接销毁,表示终止执行, 主进程退出之前,把所有的子进程直接销毁就可以了

子进程名.terminate()

import multiprocessing

import time

# 测试子进程是否执行完成以后主进程才能退出

def work():

    for i in range(10):

        print("工作中...")

        time.sleep(0.2)

if __name__ == '__main__':

    # 创建子进程

    work_process = multiprocessing.Process(target=work)

    # 设置守护主进程,主进程退出后子进程直接销毁,不再执行子进程中的代码

    # work_process.daemon = True

    work_process.start()

    # 让主进程等待1秒钟

    time.sleep(1)

    print("主进程执行完成了啦")

    # 让子进程直接销毁,表示终止执行, 主进程退出之前,把所有的子进程直接销毁就可以了

    work_process.terminate()

    # 总结: 主进程会等待所有的子进程执行完成以后程序再退出

小结:

  进程之间不共享全局变量

  主进程会等待所有的子进程执行完成程序再退出

4.进程间通信-Queue

<1>Queue的使用

可以使用multiprocessing模块的Queue实现多进程之间的数据传递,Queue本身是一个消息队列程序

首先用一个小实例来演示一下Queue的工作原理:

import multiprocessing

import time

if __name__ == '__main__':

    # 创建消息队列, 3:表示队列中最大消息个数

    queue = multiprocessing.Queue(3)

    # 放入数据

    queue.put(1)

    queue.put("hello")

    queue.put([3,5])

    # 总结: 队列可以放入任意数据类型

    # 提示: 如果队列满了,需要等待队列有空闲位置才能放入数据,否则一直等待

    # queue.put((5,6))

    # 提示: 如果队列满了,不等待队列有空闲位置,如果放入不成功直接崩溃

    # queue.put_nowait((5,6))

    # 建议: 向队列放入数据统一使用put

    # 查看队列是否满了

    # print(queue.full())

    # 注意点:queue.empty()判断队列是否空了不可靠

    # 查看队列是否空了

    # print(queue.empty())

    # 解决办法: 1. 加延时操作 2. 使用判断队列的个数,不使用empty

    # time.sleep(0.01)

    if queue.qsize() == 0:

        print("队列为空")

    else:

        print("队列不为空")

    # 获取队列的个数

    size = queue.qsize()

    print(size)

    # 获取数据

    value = queue.get()

    print(value)

    # 获取队列的个数

    size = queue.qsize()

    print(size)

    # 获取数据

    value = queue.get()

    print(value)

    # 获取数据

    value = queue.get()

    print(value)

    # 获取队列的个数

    size = queue.qsize()

    print(size)

    # 提示:如果队列空了,再取值需要等待,只有队列有值以后才能获取队列中数据

    # value = queue.get()

    # print(value)

    # queue.get_nowait()提示: 如果队列空了 ,不需要等待队列有值,但是如果取值的时候发现队列空了直接崩溃

    # 建议大家: 向队列取值使用get

    # value = queue.get_nowait()

    # print(value)

说明:

初始化Queue()对象时(例如:q=Queue()),若括号中没有指定最大可接收的消息数量,或数量为负值,那么就代表可接受的消息数量没有上限(直到内存的尽头);

  Queue.qsize():返回当前队列包含的消息数量;

  Queue.empty():如果队列为空,返回True,反之False , 注意这个操作是不可靠的。

  Queue.full():如果队列满了,返回True,反之False;

  Queue.get([block[, timeout]]):获取队列中的一条消息,然后将其从列队中移除,block默认值为True;

1)如果block(阻塞)使用默认值,且没有设置timeout(暂时休息)(单位秒),消息列队如果为空,此时程序将被阻塞(停在读取状态),直到从消息列队读到消息为止,如果设置了timeout,则会等待timeout秒,若还没读取到任何消息,则抛出"Queue.Empty"异常;

2)如果block值为False,消息列队如果为空,则会立刻抛出"Queue.Empty"异常;

  Queue.get_nowait():相当Queue.get(False);

  Queue.put(item,[block[, timeout]]):将item消息写入队列,block默认值为True;

1)如果block使用默认值,且没有设置timeout(单位秒),消息列队如果已经没有空间可写入,此时程序将被阻塞(停在写入状态),直到从消息列队腾出空间为止,如果设置了timeout,则会等待timeout秒,若还没空间,则抛出"Queue.Full"异常;

2)如果block值为False,消息列队如果没有空间可写入,则会立刻抛出"Queue.Full"异常;

  Queue.put_nowait(item):相当Queue.put(item, False);

<2>消息队列Queue完成进程间通信的演练

我们以Queue为例,在父进程中创建两个子进程,一个往Queue里写数据,一个从Queue里读数据:

import multiprocessing

import time

# 写入数据

def write_data(queue):

    for i in range(10):

        if queue.full():

            print("队列满了")

            break

        queue.put(i)

        time.sleep(0.2)

        print(i)

# 读取数据

def read_data(queue):

    while True:

        # 加入数据从队列取完了,那么跳出循环

        if queue.qsize() == 0:

            print("队列空了")

            break

        value = queue.get()

        print(value)

if __name__ == '__main__':

    # 创建消息队列

    queue = multiprocessing.Queue(5)

    # 创建写入数据的进程

    write_process = multiprocessing.Process(target=write_data, args=(queue,))

    # 创建读取数据的进程

    read_process = multiprocessing.Process(target=read_data, args=(queue,))

    # 启动进程

    write_process.start()

    # 主进程等待写入进程执行完成以后代码再继续往下执行

    write_process.join()

    read_process.start()

小结:

  从队列取值使用get方法,向队列放入值使用put方法

  消息队列判断队列是否为空不可靠,可以使用延时和根据个数进行判断

5.进程池Pool

进程池的好处:

  根据任务自动创建进程

  合理利用指定进程完成多任务

<1>进程池的概念

池子里面放的是进程,进程池会根据任务执行情况自动创建进程,而且尽量少创建进程,合理利用进程池中的进程完成多任务

(根据任务执行情况自动创建进程,并不是一开始就创建最大数量的进程)

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

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

<2>进程池同步执行任务

进程池同步执行任务表示进程池中的进程在执行任务的时候一个执行完成另外一个才能执行,如果没有执行完会等待上一个进程执行

进程池同步实例代码

import multiprocessing

import time

# 拷贝任务

def work():

    print("复制中...", multiprocessing.current_process().pid)

    time.sleep(0.5)

if __name__ == '__main__':

    # 创建进程池

    # 3:进程池中进程的最大个数,如果不设置,默认最大进程个数为电脑CPU的核数

    pool = multiprocessing.Pool(3)

    # 模拟大批量的任务,让进程池去执行

    for i in range(5):

        # 循环让进程池执行对应的work任务

        # 同步执行任务,一个任务执行完成以后另外一个任务才能执行

        pool.apply(work)

<3>进程池异步执行任务

进程池异步执行任务表示进程池中的进程同时执行任务,进程之间不会等待

异步执行,任务执行不会等待,多个任务一起执行

进程池名.apply_async(任务名)

    # 关闭进程池,意思告诉主进程以后不会有新的任务添加进来

    进程池名.close()

    # 主进程等待进程池执行完成以后程序再退出

    进程池名.join()

进程池异步实例代码

# 进程池:池子里面放的进程,进程池会根据任务执行情况自动创建进程,而且尽量少创建进程,合理利用进程池中的进程完成多任务

import multiprocessing

import time

# 拷贝任务

def work():

    print("复制中...", multiprocessing.current_process().pid)

    # 获取当前进程的守护状态

    # 提示:使用进程池创建的进程是守护主进程的状态true,默认自己通过Process创建的进程不是守护主进程的状态false

    # print(multiprocessing.current_process().daemon)

    time.sleep(0.5)

if __name__ == '__main__':

    # 创建进程池

    # 3:进程池中进程的最大个数

    pool = multiprocessing.Pool(3)

    # 模拟大批量的任务,让进程池去执行

    for i in range(5):

        # 循环让进程池执行对应的work任务

        # 同步执行任务,一个任务执行完成以后另外一个任务才能执行

        # pool.apply(work)

        # 异步执行,任务执行不会等待,多个任务一起执行

        pool.apply_async(work)

    # 关闭进程池,意思告诉主进程以后不会有新的任务添加进来

    pool.close()

    # 主进程等待进程池执行完成以后程序再退出

    pool.join()

小结:

multiprocessing.Pool常用函数解析:

  同步执行:apply(func[, args[, kwds]]): 阻塞方式调用函数,args表示以元组方式给函数传参,kwds表示以字典方式给函数传参

  异步执行:apply_async(func[, args[, kwds]]) :使用非阻塞方式调用函数,args表示以元组方式给函数传参,kwds表示以字典方式给函数传参

  close():关闭Pool,使其不再接受新的任务;

  terminate():不管任务是否完成,立即终止;

  join():主进程阻塞,等待子进程的退出, 必须在close或terminate之后使用;

6.进程、线程对比

<1>功能对比

  进程,能够完成多任务,比如 在一台电脑上能够同时运行多个QQ

  线程,能够完成多任务,比如 一个QQ中的多个聊天窗口

<2>定义对比

  进程是系统进行资源分配基本单位,每启动一个进程操作系统都需要为其分配运行资源。

  线程是CPU调度基本单位,是运行程序中的一个执行分支。

  总结:进程是操作系统资源分配的基本单位,线程是CPU调度的基本单位

<3>关系对比

  线程是依附在进程里面的,没有进程就没有线程

  一个进程默认提供一条线程,进程可以创建多个线程

<4>区别

  进程之间不共享全局变量

  线程之间共享全局变量,但是要注意资源竞争的问题,解决办法: 互斥锁或者线程同步

  创建进程的资源开销要比创建线程的资源开销要大

  进程是操作系统资源分配的基本单位,线程是CPU调度的基本单位

  线程不能够独立执行,必须依存在进程中

  多进程开发比单进程多线程开发稳定性要强

<5>优缺点

多进程:

  优点:可以用多核

  缺点:资源开销大

多线程:

  优点:资源开销小

  缺点:不能使用多核

7.文件夹拷贝器-多任务

<1>功能要求

  使用进程池完成多任务文件夹的拷贝

import os

import shutil

import multiprocessing

# import time

# 文件拷贝任务

def copy_work(src_dir, dst_dir, file_name):

    # 查看进程对象

    pid = multiprocessing.current_process().pid

    print(pid)

    # 拼接源文件的路径

    src_file_path = src_dir + "/" + file_name

    # 拼接目标文件的路径

    dst_file_path = dst_dir + "/" + file_name

    with open(dst_file_path, "wb") as dst_file:

        # 打源文件读取文件中的数据

        with open(src_file_path, "rb") as src_file:

            while True:

                # 读取数据

                src_file_data = src_file.read(1024)

                if src_file_data:

                    # 写入到目标文件里面

                    dst_file.write(src_file_data)

                else:

                    break

    # time.sleep(0.5)

if __name__ == '__main__':

    # 源目录

    src_dir = "test"

    # 目标目录

    dst_dir = "/home/python/Desktop/test"

    # 判断文件夹是否存在

    if os.path.exists(dst_dir):

        # 存在则删除文件夹及文件夹里面的所有文件

        shutil.rmtree(dst_dir)

    # 创建目标文件夹

    os.mkdir(dst_dir)

    # 获取源目录里面文件的列表

    file_name_list = os.listdir(src_dir)

    # 创建进程池

    pool = multiprocessing.Pool(3)

    # 遍历文件里面获取文件名

    for file_name in file_name_list:

        # 使用进程池执行拷贝任务,使用*args和**kwagrs传参数!此处选择元组

        pool.apply_async(copy_work, (src_dir, dst_dir, file_name))

    # 关闭进程池

    pool.close()

    # 主进程等待进程池执行完成以后程序再退出

    pool.join()

小结:

进程池在执行任务的时候会尽量少创建进程,合理利用现有进程完成多任务,这样可以减少资源开销

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容

  • 进程间通信——队列和管道(multiprocess.Queue、multiprocess.Pipe) 进程间通信 ...
    go以恒阅读 1,778评论 0 3
  • 写在前面的话 代码中的# > 表示的是输出结果 输入 使用input()函数 用法 注意input函数输出的均是字...
    FlyingLittlePG阅读 2,743评论 0 8
  • 1.进程 1.1多线程的引入 现实生活中 有很多的场景中的事情是同时进行的,比如开车的时候手和脚共同来驾驶汽车,再...
    TENG书阅读 498评论 0 0
  • 灵魂是意识的聚集!人的意识是没有发展的,散乱的,修炼就是让意识聚焦在自己身上,无论外面呈现出来的是什么,都可以照见...
    竺子阅读 150评论 0 0
  • 1. 作为一个没能力在上海置业的外来人口,搬家是每隔几年或每隔一段时间都要面临的事情。 尽管很多时候都是被动的。 ...
    娴雅居士阅读 253评论 2 2