目录

Python多任务编程(1)进程

Python多任务编程(1)进程

学习目标

  • 了解多任务
  • 多任务执行方式
  • 进程多进程使用
  • 进程的注意点
  • 总结

了解多任务

之前写的代码时顺序执行,两个函数或两个方法不能不能同时执行。多任务最大的好处就是充分利用CPU资源,提高程序的执行效率

多任务的概念:是指在同一时间内执行多个任务,例如:在操作系统中可以同时使用聊天软件、办公软件、游戏软件等,多任务保证了CPU资源的充分利用,使用户能更加有效地使用计算机软件。


多任务执行方式

  • 并发
  • 并行

并发:在一段时间内交替执行任务。例如,对于单核CPU处理多任务,操作系统轮流让各个软件交替执行,假如:软件1执行0.01秒,切换到软件2,软件2再执行0.01秒。再切换到软件3,执行0.01秒……这样反复执行下去。表面上看每个软件都是交替执行的,但是CPU的执行速度很快,我们感觉就像这些软件都在同时执行一样,单核CPU是并发执行多任务的。

单核CPU并发执行相当于只有一个人干活多个活,每个活交替执行。

并行:对于多核CPU处理多任务,操作系统会给CPU的每个内核安排一个执行的软件,多个内核是真正的一起执行的软件,这里需要注意多核CPU是并行的执行多任务,始终有多个软件一起执行

多核CPU并行执行相当于多个人干多个活,当任务数大于核心数,即并发执行任务,当任务数小于核心数,即并行执行任务


进程及多进程的使用

理解进程

进程:在Python程序中,想要实现多任务可以使用进程来完成,进程是实现多任务的一种方式。

一个正在运行的程序或者软件就是一个进程,它是操作系统进行资源分配的基本单位。也就是说每启动一个进程,操作系统都会给其分配一定的运行资源保证进程的运行。

比如:现实生活中的公司理解为一个进程,公司提供办公资源,真正干活的是员工,员工可以理解成线程。

⚠️注意:一个程序运行后至少有一个进程,一个进程默认有一个线程,进程里面可以创建多个线程,线程是依附在进程里面的,没有进程就没有线程。

比如:hello.py是一个多任务程序,开始运行后创建两个进程,则两边进程中有线程开始处理数据。

多进程的使用

使用步骤

  • 导入进程包:
1
2
# 导入进程包
import multiprocessing
  • 使用Process进程类:

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

    • group: 指定进程组,目前只能使用None
    • target:执行的目标任务名(一个函数或者一个方法)
    • name:进程名字(默认process-N)
    • args:以元组方式给执行任务传参
    • kwargs:以字典方式给执行任务传参

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

    • start():启动子进程实例
    • join():等待子进程执行结束
    • terminate():不管任务是否完成,立即终止子进程

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

    • name:当前进程的别名,默认为process-N,N为从1开始递增的整数。
  • 多进程任务代码编写:(主进程唱歌,子进程跳舞)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 导入进程包
import multiprocessing
import time

# 跳舞任务
def dance():
    for i in range(3):
        print('跳舞中...')
        time.sleep(1)
# 唱歌任务
def sing():
    for i in range(3):
        print("唱歌中...")
        time.sleep(1)

if __name__ == '__main__':
    # 创建子进程,自己手动从主进程中创建的为子进程,在__init__.py中已经导入了Process类
    dance_process = multiprocessing.Process(target=dance)
    # 子进程执行跳舞
    dance_process.start()
    # 主进程执行唱歌
    sing()
  • 获取进程编号

    获取进程编号的目的就是验证主进程和子进程之间的关系,可以得知子进程是由哪个主进程创建出来的。常用获取进程编号的两种操作有:1.获取当前进程编号,2.获取当前父进程编号。

    • os.getpid()表示获取当前进程的编号。

      • multiprocessing.current_process()获取当前进程的对象。
    • os.getppid()表示获取当前进程的父进程编号。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    
    # 导入进程包
    import multiprocessing
    import os
      
    print(f'当前进程的编号为{os.getpid()},当前进程对象为{multiprocessing.current_process()}')
    # 跳舞任务
    def dance():
        print(f'跳舞进程编号为{os.getpid()},其父进程编号为{os.getppid()},我的进程对象为{multiprocessing.current_process()}')  # 获取跳舞子进程编号
      
    # 唱歌任务
    def sing():
        print(f'唱歌进程编号为{os.getpid()},其父进程编号为{os.getppid()},我的进程对象为{multiprocessing.current_process()}')  # 获取唱歌进程的编号
      
      
    if __name__ == '__main__':
        # 创建子进程,自己手动从主进程中创建的为子进程,在__init__.py中已经导入了Process类
        dance_process = multiprocessing.Process(target=dance,name='跳舞子进程')
        # 子进程执行跳舞
        dance_process.start()
        # 主进程执行唱歌
        sing()
          
       
    """
    当前进程的编号为78193,当前进程对象为<_MainProcess name='MainProcess' parent=None started>
    唱歌进程编号为78193,其父进程编号为45755,我的进程对象为<_MainProcess name='MainProcess' parent=None started>
    当前进程的编号为78195,当前进程对象为<_MainProcess name='跳舞子进程' parent=None started>
    跳舞进程编号为78195,其父进程编号为78193,我的进程对象为<Process name='跳舞子进程' parent=78193 started>
    """
    
  • 根据进程编号杀进程(sig为9时强制进程中断):

    1
    
    os.kill(进程pid编号, sig)
    
  • 进程执行带有参数的任务:

    • ​ 什么是进程执行带有参数的任务?

    以上是使用进程执行的任务是没有参数的,假如我们使用进程执行的任务带有参数,给函数传参数有两种方式:

    1. args表示以元组的方式给执行任务传参数
    1
    2
    3
    4
    5
    6
    7
    8
    
    import multiprocessing
    def show_info(n, m):
        print(n+m)
      
    # 创建子进程
    if __name__ == '__main__':
        sub_process = multiprocessing.Process(target=show_info,args=(1,2))
        sub_process.start() # 3
    
    1. kwargs表示以字典方式给执行任务传参数
    1
    2
    3
    4
    5
    6
    7
    8
    
    import multiprocessing
    def show_info(n, m):
        print(n+m)
      
    # 创建子进程
    if __name__ == '__main__':
        sub_process = multiprocessing.Process(target=show_info,kwargs={'n':1,'m':2})
        sub_process.start() # 3
    

进程的注意点

进程的注意点有两点:

  1. 进程之间不共享全局变量。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import multiprocessing

# 定义全局变量
g_list= []
def add_data():
    for i in range(3):
        g_list.append(i) # 列表为可变类型
def read_data():
    print(g_list)

if __name__ == '__main__':
    first_process = multiprocessing.Process(target=add_data)
    second_process = multiprocessing.Process(target=read_data)
    first_process.start()
    first_process.join() # 主进程等待第一个进程结束完
    second_process.start()			# []为空列表

⚠️注意:主进程里面有g_list变量、add_data函数和read_data函数,创建两个子进程都为主进程资源的拷贝,子进程其实就是主进程的一个副本,也就是说读取数据的函数所利用到的g_list列表仍然为空的,因此进程之间数据不可共享。

对于linux和mac主进程执行的代码,不会进行拷贝(py3.6),但是对于对于windows来说主进程执行的代码也会进行拷贝,也就是创建子进程对象的代码也会进行拷贝,因此是一个递归操作无限制执行,会报错。

解决以上问题就应该将创建子进程对象的代码放在判断主模块的代码中,即在发生调用时因为代码在判断主模块中,拷贝操作不会将调用的代码继续拷贝下去,不会发生递归操作。

  1. 主进程会等待所有的子进程执行结束再结束。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import multiprocessing
import time

def child_task():
    for i in range(10):
        print('任务执行中。。。')
        time.sleep(0.2)
    print('子进程结束')


# 创建子进程
if __name__ == '__main__':
    child_process = multiprocessing.Process(target=child_task)
    child_process.start()

    time.sleep(1)
    print('over')
"""
任务执行中。。。
任务执行中。。。
over
任务执行中。。。
任务执行中。。。
任务执行中。。。
任务执行中。。。
任务执行中。。。
任务执行中。。。
任务执行中。。。
任务执行中。。。
子进程结束
"""

如果子进程是死循环,将子进程设置为守护进程,主进程销毁,子进程跟着销毁。

1
2
3
4
5
6
if __name__ == '__main__':
    child_process = multiprocessing.Process(target=child_task)
    child_process.daemon = True
    child_process.start()
    time.sleep(1)
    print('over')

或者在退出主进程之前将子进程销毁。

1
2
3
4
5
6
7
if __name__ == '__main__':
    child_process = multiprocessing.Process(target=child_task)
    # child_process.daemon = True
    child_process.start()
    time.sleep(1)
    child_process.terminate() # 销毁
    print('over')

总结

  • 多任务是指充分利用CPU资源,将两个函数或者两个方法进行同时执行。
  • 对于多任务的执行方式,Python有两种,一种是并发、一种是并行;并发是指将多个任务交替执行,并行时将多个任务分配给多个核心去执行,当任务数大于核心数,那么进行并发执行,如果任务数小于核心数,则使用并行执行。
  • 进程是实现Python多任务的一种方式,每个程序开始运行都有主线程,从主进程我们可以创建子进程拷贝,进程是一种资源单位,每一个进程都至少有一个线程,进程不是任务的执行者,线程才是任务的执行者。
  • Python用multiprocessing进程包来实现子进程创建要求,通过multiprocessingProcess类来制定需要创建子进程来执行的目标函数,指定进程名,以及传参数等操作的实例对象。
  • 在进程的使用中我们会使用获取当前进程编号的os.getpid()函数,使用os.getppid()获取当前进程的父进程,使用os.kill()函数杀死进程。
  • 注意两点,一是因为子进程是主进程的拷贝,进程之间不共享全局变量,二是主进程会等待所有的子进程执行结束再结束;为了避免拷贝导致的程序递归,常把子线程的实例对象创建代码和子线程运行代码写入main判断中。