第十二篇:线程和进程之多进程(二)

  本篇主要介绍关于多进程的相关知识,同时需对进程与线程之间的关系和区别及应用进行了解,其实包含 multiprocessing模块下的 Process类、进行进程间通信的 Queue类以及进程池 Pool类的学习,最后实现一个实例 模拟文件夹copy器;

一、什么是进程?

  首先我们在了解进程之前,我们需要了解一下什么是程序?

  程序:

    其实就是一堆躺在操作系统下的二进制文件,是静态的;例如 wechar.exe文件,在我们没点击它时,就是一个静态的可执行文件。

  进程:

    进程就是跑起来的程序,即代码 + 操作系统根据其需求为其分配的资源称之为进程;例如:当一个 Wechat.exe文件我们点击运行时,操作系统会为其调配其所需资源,则运行该程序就是一个进程;进程是操作系统分配资源的基本单位。

  需要注意的是:同一个程序执行两次,那就是两个进程;例如 打开腾讯视频--》一个可以播放西游记,一个可以播放红楼梦;

  进程的状态:

    在现实工作中,任务的数量要远远大于CPU的数量,所以要想实现真正意义上的并行几乎是不可能的,所以大多数情况下都是并发(伪并行),故当运行多个进程时,有的程序处于等待的状态、有的程序处于运行的状态,则进程就可以分为以下几种状态:

  

 三种状态:

  就绪:所有准备工作均完成,等待着操作系统分配CPU进行执行;

  运行:正在被CPU执行的程序

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


 

二、多进程的创建过程:、

  我们创建多进程主要通过模块 multiprocessing 下的Process类来创建进程对象,通过进程对象来实现创建进程的效果,不多说直接看示例,例如:

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import multiprocessing,time

def sing(name):
    for i in range(10000):
        print("%s is singing --->%s"%(name,i))
        # time.sleep(0.5)

def dance(name):
    for i in range(10000):
        print("%s is dancing ---> %s"%(name,i))
        # time.sleep(0.5)

def main():
    # 1、创建进程对象,传入工作函数,以及工作函数所需参数
    p1 = multiprocessing.Process(target=sing,args=("alex",))
    p2 =multiprocessing.Process(target=dance,args=("liudehua",))

    # 2、启动进程
    p1.start()
    p2.start()
    print("This is main process...")

    # 3、主进程等子进程运行完才关闭
    p1.join()
    p2.join()

if __name__=="__main__":
    main()

  从上述例子中可以看出:进程的创建过程与线程的创建过程几乎相同,除了使用模块不相同,其他的几乎类似。那么既然有了线程为什么还要有进程呢?接下来我们就了解一下进程和线程的区别。


三、线程和进程的区别及应用场景

  首先我们需要知道无论是线程还是进程均可以实现多任务的效果,但是一个进程必然有一个线程,同时一个进程也可以有多个线程,例如:一个WeChat可以打开多个聊天窗口,与不同的人进行聊天。其关系如下图:

  

 从上面的图我们可以很明确的看出进程与线程之间的关系和区别:

   1、一个程序至少有一个进程(当然也可以有多个进程,例如:一个QQ可以同时登入两个用户;),一个进程至少有一个线程(当然也可由多个线程,例如:一个QQ可以同时与两个人进行聊天。)

   2、线程无法独立存在,只能依附在进程内执行,即在QQ没有运行的情况下QQ聊天是无法实现的。

   3、一个进程的运行需要调配的资源要多得多,但是进程要稳定许多,即一个进程的关闭不会影响另外一个进程的运行,即关闭网易云音乐队天天静听的执行没有任何影响;但是一个主线程的关闭必然会导致所有的子线程死亡,稳定性不如进程。

   4、线程可以共享全局变量,而进程间无法全局变量的共享;(要想实现进程间的通信需使用队列等,在后面介绍。)

   5、进程是操作系统进行资源管理的基本单位,而线程是对操作系统的分配下来的资源调用的基本单位。

 其实我们也可以将进程和线程比喻成:车间流水线的工作(进程) 和  流水线上工人的工作(线程):

  

 个人理解: 

   可以将进程比如成流水线的运行,即在该流水线的运行需要各种资源(即进程的运行需要操作系统调配资源),以及许多工人,这些工人在不停的工作(线程),他们对该流水线上的资源是共享的(线程间共享全局变量),同时若想提高流水线的运行速度,则可以为流水线增加一些工人(多线程),而同时若想提高车间的生产能力,则可以通过增加多条流水线(多进程),但是每增加一条流水线,所需的成本也高一些(即进程需调配的资源增加),同时每个流水线上的资源是不共享的(即进程间不共享全局变量。)。


4、获取进程的PID和PPID

  每个进程均会有进程号以及父进程号,

 


 

5、Queue实现进程间的通信

  首先,我们需要知道进程间不共享全局变量,例如:网易云音乐无法播放酷狗中的音乐,接下来我们从一个例子中验证: 

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import multiprocessing,time

# 创建全局变量
name =["alex","little-five","amanda"]

def work1():
    # work1修改全局变量
    name.append("hello")
    print(name)

def work2():
    # work2查看全局变量
    print("in the work2:--> ",name)

def main():
    p1 = multiprocessing.Process(target=work1)
    p2 = multiprocessing.Process(target=work2)

    p1.start()
    time.sleep(1)  # 休眠1s,保证进程p1先运行
    p2.start()
    print("in main process : ",name)
    p1.join()
    p2.join()

if __name__=="__main__":
    main()

  从输出结果可以得出 : 进程间不共享全局变量,即即使主进程与子进程间也不共享全局变量。这是由于:

  进程每创建子线程时,老版本内核会相当于将主进程的资源和代码copy一份,但每个子线程运行的代码位置不同,但是新版本则不会拷贝代码而是代码共享,而当某个进程要修改代码时,则是相应的代码进行拷贝即写时拷贝。

  那么我们怎么实现进程间的通信呢?

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import multiprocessing

def recv_msg(q):
    """数据的接收"""
    names =["alex","wupeiqi","linghaifeng"]
    for name in names:
        q.put(name)  #将每个名字传入队列

def analysis_msg(q):
    """数据的分析"""
    new_names =list()
    while not q.empty():
        new_name=q.get()  # 获取队列中的名字
        new_names.append(new_name)
    print(list(map(lambda x:x.title(),new_names)))  #通过map 函数对列表进行处理


q=multiprocessing.Queue()  # 创建队列对象

def main():
    p1 = multiprocessing.Process(target=recv_msg,args=(q,))  #我们将队列作为工作函数的参数传入进程
    p2 = multiprocessing.Process(target=analysis_msg,args=(q,))  

    p1.start()
    p2.start()

    p1.join()
    p2.join()

if __name__=="__main__":
    main()

  注:其实队列就像一根水管,即先入先出,后入后出,数据从水管的一边传入从另一边输出,通过队列来实现进程间的数据传递,实现进程间的通信。

猜你喜欢

转载自www.cnblogs.com/littlefivebolg/p/9298606.html