多任务之进程

进程


进程的概念

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

注意:

一个程序至少有一个进程,一个进程至少有一个线程,多进程可以完成多任务.
一个进程默认有一个线程,进程里面可以创建线程,线程是依附在进程里面的,
没有进程就没有线程。
进程的使用

1 . 导入模板

#导入线程模块
import multiprocessing

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

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

group:指定进程组,目前只能使用None
target:执行的目标任务名
name:进程名字
args:以元组方式给执行任务传参
kwargs:以字典方式给执行任务传参

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

start():启动子进程实例(创建子进程)
join([timeout]):是否等待子进程执行结束,或等待多少秒
terminate():不管任务是否完成,立即终止子进程
Process创建的实例对象的常用属性:

name:当前进程的别名,默认为Process-N,N为从1开始递增的整数
pid:当前进程的pid(进程号)
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()

执行结果:

古力娜扎 18
注意点

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: []

注意:

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

2 . 主进程会等待所有子进程执行完后才结束

进程间通信-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)
    # 提示: 如果队列空了 ,不需要等待队列有值,但是如果取值的时候发现队列空了直接崩溃
    # 建议大家: 向队列取值使用get
    # value = queue.get_nowait()
    # print(value)

执行结果:

队列不为空
3
1
2
hello
[3, 5]
0

特别提醒:

初始化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);

利用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()

执行结果:

0
1
2
3
4
队列满了
0
1
2
3
4
队列空了

总结:

从队列取值使用get方法,向队列放入值使用put方法
消息队列判断队列是否为空不可靠,可以使用延时和根据个数进行判断

进程池

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

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

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

1 . 进程池同步执行代码

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


import multiprocessing
import time


# 拷贝任务
def work():
    print("复制中...", multiprocessing.current_process().pid)
    time.sleep(0.5)

if __name__ == '__main__':
    # 创建进程池
    # 3:进程池中进程的最大个数
    pool = multiprocessing.Pool(3)
    # 模拟大批量的任务,让进程池去执行
    for i in range(5):
        # 循环让进程池执行对应的work任务
        # 同步执行任务,一个任务执行完成以后另外一个任务才能执行
        pool.apply(work)

执行结果:

复制中... 100512
复制中... 68128
复制中... 98924
复制中... 100512
复制中... 68128

2 . 进程池异步执行代码

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

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


# 拷贝任务
def work():
    print("复制中...", multiprocessing.current_process().pid)
    # 获取当前进程的守护状态
    # 提示:使用进程池创建的进程是守护主进程的状态,默认自己通过Process创建的进程是不是守住主进程的状态
    # 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()

执行结果:

复制中... 122872
复制中... 61772
复制中... 114636
复制中... 122872
复制中... 114636

小结:
multiprocessing.Pool常用函数解析:

apply(func[, args[, kwds]]): 阻塞方式调用函数,args表示以元组方式给函数传参,kwds表示以字典方式给函数传参
apply_async(func[, args[, kwds]]) :使用非阻塞方式调用函数,args表示以元组方式给函数传参,kwds表示以字典方式给函数传参
close():关闭Pool,使其不再接受新的任务;
terminate():不管任务是否完成,立即终止;
join():主进程阻塞,等待子进程的退出, 必须在close或terminate之后使用;

进程线程对比

  1. 功能对比

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

  2. 定义对比

    进程是系统进行资源分配基本单位,每启动一个进程操作系统都需要为其分配运行资源。
    线程是运行程序中的一个执行分支,是CPU调度基本单位。
    总结:进程是操作系统资源分配的基本单位,线程是CPU调度的基本单位

  3. 关系对比

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

  4. 区别

    进程之间不共享全局变量
    线程之间共享全局变量,但是要注意资源竞争的问题,解决办法: 互斥锁或者线程同步
    创建进程的资源开销要比创建线程的资源开销要大
    进程是操作系统资源分配的基本单位,线程是CPU调度的基本单位
    线程不能够独立执行,必须依存在进程中
    多进程开发比单进程多线程开发稳定性要强

优缺点

多进程:

优点:可以用多核
缺点:资源开销大

多线程:

优点:资源开销小
缺点:不能使用多核
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值