六、python学习之多任务-进程

版权声明:浅弋、璃鱼的原创 https://blog.csdn.net/qq_35709559/article/details/82287420

一、进程的介绍:

1.进程的概念:

进程是向操作系统索要运行资源,给线程使用的。进程只提供资源,真正执行任务的是线程。即:进程是操作系统分配资源的基本单位。

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

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

2.进程的状态:

2.1概念:

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

就绪态:运行的条件都已经满足,等待cpu的调用

执行态:得到cpu的调用,正在执行其功能

等待态:因需要等待某些条件满足而处于一种阻塞的状态,例如:一个程序sleep了、input函数等待键盘输入,此时就处于等待态。

二、进程的使用:

1.进程完成多任务:

1.1导包:

# 导包,导入进程模块
import multiprocessing as mult    # 这里可以使用as定义一个别名,使用时直接使用mult

1.2 创建Process进程类:

# Process进程类的语法结构:
sub_process = mult.Process(([group [, target [, name [, args [, kwargs]]]]]))
# group:指定进程组,目前只能使用None
# target:执行的目标任务名
# name:进程名字
# args:以元组方式给执行任务传参
# kwargs:以字典方式给执行任务传参

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

sub_process.start()    # 启动子进程实例(启动子进程)
sub_process.join([timeout])    # 是否等待子进程执行结束,或等待多少秒
sub_process.terminate()    # 不管任务是否完成,立即终止子进程

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

sub_process.name    # 当前进程的别名,默认Process-N,N为从1开始递增的整数
sub+process.pid    # 当前进程的pid(进程号)

1.3 多进程完成多任务:

代码:主进程创建子进程

#!/usr/bin/env python
# coding=utf-8

import multiprocessing as mult
import time


# 定义跳舞任务
def dance():
    # 打印跳舞任务中,当前运行的线程
    print("dance:", mult.current_process())
    for i in range(3):
        print("跳舞中...")


# 定义唱歌任务
def sing():
    # 打印唱歌任务中,当前运行的线程
    print("sing:", mult.current_process())
    for i in range(3):
        print("唱歌中...")


if __name__ == '__main__':
    print("main:", mult.current_process())
    # 创建子进程
    dance_thread = mult.Process(target=dance)
    sing_thread = mult.Process(target=sing)

    print("拿到线程对象:", dance_thread, sing_thread)

    # 开启进程
    dance_thread.start()
    sing_thread.start()

运行结果:

main: <_MainProcess(MainProcess, started)>
拿到线程对象: <Process(Process-1, initial)> <Process(Process-2, initial)>
dance: <Process(Process-1, started)>
跳舞中...
跳舞中...
跳舞中...
sing: <Process(Process-2, started)>
唱歌中...
唱歌中...
唱歌中...

1.4 获取进程的pid:

代码:

#!/usr/bin/env python
# coding=utf-8
import multiprocessing as mult
import time
import os


def dance():
    print("dance:", mult.current_process())
    # 获取当前进程的pid
    print("dance的pid:", mult.current_process().pid)
    # 获取当前进程的父id
    print("dance的父pid:",os.getppid())

    for i in range(3):
        print("跳舞中...")


def sing():
    print("sing", mult.current_process())
    # 获取当前进程的pid
    print("sing的pid:", os.getpid())
    # 获取当前进程的父id
    print("sing的父pid:", os.getppid())

    for i in range(3):
        print("唱歌中...")
        # 杀死进程:9 即发送的信号量,强制杀死进程
        os.kill(os.getpid(), 9)


if __name__ == '__main__':
    print("main:", mult.current_process())
    # 创建子进程
    dance_thread = mult.Process(target=dance)
    sing_thread = mult.Process(target=sing)

    print(dance_thread, sing_thread)

    # 开启进程
    dance_thread.start()
    sing_thread.start()

运行结果:

main: <_MainProcess(MainProcess, started)>
<Process(Process-1, initial)> <Process(Process-2, initial)>
dance: <Process(Process-1, started)>
dance的pid: 4912
dance的父pid: 8796
跳舞中...
跳舞中...
跳舞中...
sing <Process(Process-2, started)>
sing的pid: 7100
sing的父pid: 8796
唱歌中...

获取当前进程的pid:mult.current_process().getpid ==> os.getpid()
通过mult.current_process().getpid是属性

获取父进程的ppid: os.getppid()

2.给子进程指定的函数传参:

代码:

#!/usr/bin/python3
# coding=utf-8

import multiprocessing

def show_info(name, age):
    print(name, age)


if __name__ == '__main__':
    # 创建子进程,指定执行对应的任务
    # 使用位置参数传参
    # sub_process = multiprocessing.Process(target=show_info, args=("贾玲", 18))
    # sub_process.start()

    # 使用关键字参数传参
    sub_process = multiprocessing.Process(target=show_info, kwargs={"name": "韩红", "age": 18})
    sub_process.start()

三、进程的注意点:

1.进程之间不共享全局变量:

创建子进程其实是对主进程进行拷贝,进程之间相互独立,访问的全局变量不是同一个,所以进程之间不共享全局变量。

代码:

#!/usr/bin/env python
# coding=utf-8

import multiprocessing as mult
import time

# 定义全局变量
g_list = list()


# 定义添加数据
def add_data():
    for name in ["陈晓", "杨洋", "小岳岳"]:
        g_list.append(name)
        print(name)
        time.sleep(0.2)
    print("add_data:", g_list, "拿一下add线程中的全局变量id:", id(g_list))

# 读取数据
def read_data():
    # 访问全局变量
    print("read_data:", g_list, "拿一下read线程中的全局变量id:", id(g_list))


if __name__ == '__main__':
    print("开始,拿一下main线程中的全局变量id:", id(g_list))
    # 创建子进程
    add_thread = mult.Process(target=add_data)
    read_thread = mult.Process(target=read_data)

    # 启动线程
    add_thread.start()
    # 设置进程等待,等待添加数据完成
    add_thread.join()
    read_thread.start()

    print("最后,拿一下main线程中的全局变量id:", id(g_list))
    # 打印主进程中的全局变量
    print("main:", g_list)

运行结果:

开始,拿一下main线程中的全局变量id: 1374250461192
陈晓
杨洋
小岳岳
add_data: ['陈晓', '杨洋', '小岳岳'] 拿一下add线程中的全局变量id: 2913345090248
最后,拿一下main线程中的全局变量id: 1374250461192
main: []
read_data: [] 拿一下read线程中的全局变量id: 2480673272520

2.主进程会等待所有子进程执行完成后程序再退出:

2.1 主进程会等待子进程执行完成后程序再退出:

代码:

#!/usr/bin/env python
# coding=utf-8

import multiprocessing as mult
import time


# 定义复杂任务1
def task1():
    for i in range(5):
        print("任务1正在执行...")
        time.sleep(0.2)


# 定义复杂任务2
def task2():
    for i in range(5):
        print("任务2正在执行...")
        time.sleep(0.2)


if __name__ == '__main__':
    # 创建子进程
    first_process = mult.Process(target=task1)
    second_process = mult.Process(target=task2)

    # 执行线程
    first_process.start()
    second_process.start()

    # 主进程执行0.3秒后结束
    time.sleep(0.3)
    print("over...")
    exit()
    print(mult.current_process())

运行结果:

任务1正在执行...
任务2正在执行...
over...
任务1正在执行...
任务2正在执行...
任务1正在执行...
任务2正在执行...
任务1正在执行...
任务2正在执行...
任务1正在执行...
任务2正在执行...

做了一件傻事,在exit()后写了print(mult.current_process())打印一下当前运行进程,当然并没有执行。再来看,当把其中的一个进程设置成守护进程会有一个什么效果:

2.2 多个子进程运行,设置一个守护主进程,主进程执行exit()后,守护主进程不在执行:

代码:

...
if __name__ == '__main__':
    # 创建子进程
    first_process = mult.Process(target=task1)
    # 1.通过属性,把frist_process设置成守护主进程
    first_process.daemon = True
    
    # 2.在实例化对进程的时候就可以通过构造方法,直接将线程设置成守护主进程
    # first_process = mult.Process(target=task1, daemon = True)

    second_process = mult.Process(target=task2)

    # 执行线程
    first_process.start()
    second_process.start()

    # 主进程执行0.3秒后结束
    time.sleep(0.3)
    print("over...")
    exit()

运行结果:

任务1正在执行...
任务2正在执行...
over...
任务2正在执行...
任务2正在执行...
任务2正在执行...
任务2正在执行...

看结果,task1执行了1次,也就是运行了大约0.2秒,而主进程在大约0.3秒后退出,作为守护进程的first_process就没有再执行,也就是被销毁退出了,而不是守护主进程的second_process执行完之后,主程序完全退出。

2.3 销毁子进程:

如果有需求,需要主进程执行退出后,程序直接退出,在此提供两个方法:将子进程设置成守护主进程;手动销毁子进程。

将子进程设置成守护主进程的方法在上面的代码中有体现,只需要分别将所有的子进程的deamon属性都设置为True即可。下面看一下手动销毁子进程:

work_process.terminate()

代码:

if __name__ == '__main__':
    # 创建子进程
    first_process = mult.Process(target=task1)
    second_process = mult.Process(target=task2)

    # 执行线程
    first_process.start()
    second_process.start()

    # 主进程执行0.3秒后结束
    time.sleep(0.3)
    print("over...")

    # 手动销毁子进程
    first_process.terminate()
    second_process.terminate()
    exit()

运行结果:

任务1正在执行...
任务2正在执行...
over...

当子进程被主进程销毁后,主进程执行exit()程序如期退出。

四、进程间通信-Queue(消息队列):

1.Queue的使用:

不同电脑之间进程的通信使用socket(套接字),同一台电脑上进程之间的通信使用Queue(消息对列)

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

Queue在数据结构上是一个队列,具有“先进先出”的特点。

1.1 导包:

import multiprocessing as mult

1.2 创建消息队列:消息队列是全局的

# 创建消息队列
queue = mult.Queue(3)    # 默认参数是-1,表示数量没有上限

1.3放入数据:

Queue.put(obj)    # 队满后阻塞
Queue.put_nowait(obj)    # 当队列放入不成功后,抛出Full异常

1.4 取出数据:

Queue.get()    # ==> obj: 出队,队空后进程阻塞
Queue.get_nowait()    # ==> obj: 队空后,抛出队空异常

1.5获取当前消息队列的消息数量:

Queue.qsize()    # ==>num:int

1.6 判断对满/空:

Queue.full()    # ==> bool:判队满
Queue.empty()    # ==> bool: 判对空,判断时可能出错,需要加个延时

2.使用消息队列完成进程间数据的通信:

代码:

#!/usr/bin/env python
# coding=utf-8

import multiprocessing as mult
import time


# 向消息队列里添加数据
def add_data(current_queue):
    for i in range(5):
        # 判断队满
        if current_queue.full():
            print("消息队列满了。。。")
            break
        # 队列不满,添加数据
        current_queue.put(i)
        print("wirte:", i)
        time.sleep(0.2)

# 向指定数据中读取数据
def read_data(current_queue):
    while True:
        # 判队空
        if current_queue.qsize() == 0:
            print("队列空了...")
            break

        # 队不空
        value = current_queue.get()
        print("read:", value)


if __name__ == '__main__':
    # 创建全局的消息队列
    queue = mult.Queue(3)
    # 创建写入和读取进程
    write_process = mult.Process(target=add_data, args=(queue,))
    read_process = mult.Process(target=read_data, args=(queue,))

    # 启动进程
    write_process.start()
    # 进程等待,完成写入
    write_process.join()

    read_process.start()

运行结果:

wirte: 0
wirte: 1
wirte: 2
消息队列满了。。。
read: 0
read: 1
read: 2
队列空了...

五、进程池-Pool

1.进程池的概念:

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

由于线程预先被创建并放入线程池中,同时处理完当前任务之后并不销毁而是被安排处理下一个任务,因此能够避免多次创建线程,从而节省线程创建和销毁的开销,能带来更好的性能和系统稳定性。

2.进程池同步执行任务:

代码:

#!/usr/bin/env python
# coding=utf-8

import multiprocessing as mult
import time

# 定义拷贝任务
def copy_file():
    time.sleep(0.3)
    print("文件拷贝成功", mult.current_process().pid)
    # 获取进程池的守护状态
    print(mult.current_process().daemon)


if __name__ == '__main__':
    # 创建进程池
    # 3: 表示进程池中的进程个数, 默认是cpu核数
    pool = mult.Pool(3)
    # 模拟大量任务让进程池同步执行
    for i in range(10):
        # 进程池使用同步的方式执行任务,进程池中的一个进程执行完之后下一个进程才开始执行
        pool.apply(copy_file)

运行结果:

文件拷贝成功 15792
True
文件拷贝成功 7632
True
文件拷贝成功 15824
True
文件拷贝成功 15792
True
文件拷贝成功 7632
True
文件拷贝成功 15824
True
文件拷贝成功 15792
True
文件拷贝成功 7632
True
文件拷贝成功 15824
True
文件拷贝成功 15792
True

在结果中看出,进程的pid始终是15792,7632和15824这三个值,与设置的进程池的进程数一致。同时,通过打印进程池的守护进程属性可以看出,进程池默认的就是守护主进程,因此,当主进程运行结束,进程池随即退出,符合全局变量的特性。

3.进程池异步执行任务

代码:

#!/usr/bin/env python
# coding=utf-8

import multiprocessing as mult
import time

# 定义拷贝任务
def copy_file():
    time.sleep(0.3)
    print("文件拷贝成功", mult.current_process().pid)
    # 获取进程池的守护状态
    print(mult.current_process().daemon)


if __name__ == '__main__':
    # 创建进程池
    # 3: 表示进程池中的进程个数, 默认是cpu核数
    pool = mult.Pool(3)
    # 模拟大量任务让进程池同步执行
    for i in range(10):
        # 进程池异步执行,多个任务一起执行,进程之间执行的时候不会有等待
        pool.apply_async(copy_file)
    # time.sleep(10)
    print("主进程结束...")

运行结果:

主进程结束...

哈哈,,问题来了,程序只打印了主进程的输出,是子进程没有执行吗?不是的,原因是异步状态下的子进程没有来得及执行。那同步执行为什么可以执行呢?

简单点说就是同步的p.apply()函数用于传递不定参数,主进程会被阻塞直到函数执行结束;而异步的p.apply_async()函数虽然与apply用法一样,但它是非阻塞且支持结果返回进行回调。也就是说,异步执行,不会对主进程产生阻塞。

解决方法:

pool.close() # 关闭进程池,是进程池不再接收新的进程
pool.join() # 主线程等待进程池执行完成,即主进程阻塞
#!/usr/bin/env python
# coding=utf-8

import multiprocessing as mult
import time

# 定义拷贝任务
def copy_file():
    time.sleep(0.3)
    print("文件拷贝成功", mult.current_process().pid)
    # 获取进程池的守护状态
    print(mult.current_process().daemon)


if __name__ == '__main__':
    # 创建进程池
    # 3: 表示进程池中的进程个数, 默认是cpu核数
    pool = mult.Pool(3)
    # 模拟大量任务让进程池同步执行
    for i in range(10):
        # 进程池异步执行,多个任务一起执行,进程之间执行的时候不会有等待
        pool.apply_async(copy_file)

    # time.sleep(10)
    pool.close() # 关闭进程池,是进程池不再接收新的进程
    pool.join() # 主线程等待进程池执行完成,即主进程阻塞

    print("主进程结束...")

运行结果:

文件拷贝成功 13380
True
文件拷贝成功 6488
True
文件拷贝成功 4348
True
文件拷贝成功 13380
True
文件拷贝成功 6488
True
文件拷贝成功 4348
True
文件拷贝成功 13380
True
文件拷贝成功 6488
True
文件拷贝成功 4348
True
文件拷贝成功 13380
True
主进程结束...

六、进程、线程的对比:

1.功能对比:

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

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

2.定义对比:

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

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

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

3.关系对比:

线程是依附在进程中的,没有进程就没有线程。

一个程序至少有一个线程,一个线程至少有一个进程

4.区别:

进程之间不共享全局变量

线程之间共享全局变量,但要注意资源竞争的问题,解决办法:线程等待、互斥锁

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

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

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

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

在设置守护上,守护主线程可以使用setDaemon(True)和使用__init__()设置;而守护主进程只能使用属性=True设置守护

5.优缺点:

多进程:

优点:可以用多核

缺点:资源开销大

多线程:

优点:资源开销小

缺点:不能使用多核

 

猜你喜欢

转载自blog.csdn.net/qq_35709559/article/details/82287420