3.2 多任务编程

3.2.1 多任务介绍

1. 多任务概念

同一时间内执行多个任务

2.多任务执行方式

  • 并发:在一段时间内,交替去执行任务
  • 并行:对于多核cpu处理任务,多个内核同时执行同一软件,多个任务同时执行

3.2.2 进程

1.介绍

在Python里,实现多任务的一种方式

2.概念

他是操作系统进行资源分配的一个基本单位,一个程序背后至少有一个进程,一个进程默认有一个线程,线程是依附在进程里的。

3.多进程的使用

1.导入进程包
import multiprocessing
2.Process进程类的说明

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

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

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

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

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

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

我们进去类里看初始化方法,可以发现参数均有缺省值,因为我们暂时不用传参group,所以我们使用关键字传参。

# 多任务:同一时间多个任务一起执行(唱歌的同时跳舞)

# 1.导入模块
# 2.创建进程对象
# 3.启动进程(创建子进程)

# 注意: 程序启动后,默认创建一个主进程, 多个进程运行是无序的
# target: 创建的进程对象所要执行的任务名(函数名)

# 进程对象.start() : 启动进程对象(创建子进程)


import time
# 1.导入模块
import multiprocessing


def sing():
    for i in range(3):
        print("唱歌...")
        time.sleep(1)


def dance():
    for i in range(3):
        print("跳舞...")
        time.sleep(1)


if __name__ == '__main__':

    # 2.创建进程对象
    #   target: 创建的进程对象所要执行的任务名(函数名)
    sing_process = multiprocessing.Process(target=sing)
    dance_process = multiprocessing.Process(target=dance)

    # 3.启动进程(创建子进程)
    sing_process.start()    # 真正创建sing的子进程
    dance_process.start()   # 真正创建dance的子进程


# 注意: 程序启动后,默认创建一个主进程, 多个进程运行是无序的

4.获取进程的编号 os.getpid()

1. 获取进程编号目的
  • 获取当前进程编号
  • 获取当前父进程编号
  • 验证主进程与子进程的关系
2.代码实操
# 多任务:同一时间多个任务一起执行(唱歌的同时跳舞)

# 1.导入模块
# 2.创建进程对象
# 3.启动进程(创建子进程)

# 注意: 程序启动后,默认创建一个主进程, 多个进程运行是无序的
# target: 创建的进程对象所要执行的任务名(函数名)

# 进程对象.start() : 启动进程对象(创建子进程)


import time
# 1.导入模块
import multiprocessing
# 导入os模块
import os


def sing():
    print("sing,",os.getpid())
    for i in range(3):
        print("唱歌...")
        time.sleep(1)


def dance():
    print("dance,", os.getpid())
    for i in range(3):
        print("跳舞...")
        time.sleep(1)


if __name__ == '__main__':

    print("main,", os.getpid())

    # 2.创建进程对象
    #   target: 创建的进程对象所要执行的任务名(函数名)
    sing_process = multiprocessing.Process(target=sing)
    dance_process = multiprocessing.Process(target=dance)

    # 3.启动进程(创建子进程)
    sing_process.start()    # 真正创建sing的子进程
    dance_process.start()   # 真正创建dance的子进程


# 注意: 程序启动后,默认创建一个主进程, 多个进程运行是无序的
输出
main, 13101
sing, 13103
唱歌...
dance, 13104
跳舞...
唱歌...
跳舞...
跳舞...唱歌...

可以看出进程编号跟实现对象的顺序有关系

3.获取父进程的编号 os.getppid()
# 多任务:同一时间多个任务一起执行(唱歌的同时跳舞)

# 1.导入模块
# 2.创建进程对象
# 3.启动进程(创建子进程)

# 注意: 程序启动后,默认创建一个主进程, 多个进程运行是无序的
# target: 创建的进程对象所要执行的任务名(函数名)

# 进程对象.start() : 启动进程对象(创建子进程)


import time
# 1.导入模块
import multiprocessing
# 导入os模块
import os


def sing():
    # 获取子进程sing的编号
    print("sing,",os.getpid())
    # 获取进程的父进程编号
    print("sing的父进程,", os.getppid())
    for i in range(3):
        print("唱歌...")
        time.sleep(1)


def dance():
    # 获取子进程dance的编号
    print("dance,", os.getpid())
    # 获取进程的父进程编号
    print("dance的父进程,", os.getppid())
    for i in range(3):
        print("跳舞...")
        time.sleep(1)


if __name__ == '__main__':
    # 获取主进程的编号
    print("main,", os.getpid())
    # 获取进程的父进程编号
    print("主进程的父进程,", os.getppid())

    # 2.创建进程对象
    #   target: 创建的进程对象所要执行的任务名(函数名)
    sing_process = multiprocessing.Process(target=sing)
    dance_process = multiprocessing.Process(target=dance)

    # 3.启动进程(创建子进程)
    sing_process.start()    # 真正创建sing的子进程
    dance_process.start()   # 真正创建dance的子进程


# 注意: 程序启动后,默认创建一个主进程, 多个进程运行是无序的
输出
main, 13484
主进程的父进程, 6636
sing, 13486
sing的父进程, 13484
唱歌...
dance, 13487
dance的父进程, 13484
跳舞...
唱歌...
跳舞...
唱歌...
跳舞...

可以看出,sing和dance的父进程都是我们程序的主进程,而主进程还有一个父进程,实际上基本每个程序都有一个父进程,我们在pycharm里执行的,所以应该是为pycharm的子进程,而我们追溯到最前面,大致为系统内核的进程1.

拓展

我们可以通过ps aux来查到pid为1的进程,还可以通过ps -ef查看所有程序的进程与其父进程。

pstree命令可以直观看出一个进程的树状图。

4.获取当前进程对象 multiprocessing.current_process()
# 多任务:同一时间多个任务一起执行(唱歌的同时跳舞)

# 1.导入模块
# 2.创建进程对象
# 3.启动进程(创建子进程)

# 注意: 程序启动后,默认创建一个主进程, 多个进程运行是无序的
# target: 创建的进程对象所要执行的任务名(函数名)

# 进程对象.start() : 启动进程对象(创建子进程)


import time
# 1.导入模块
import multiprocessing
# 导入os模块
import os


def sing():
    # 获取子进程sing的编号
    print("sing,",os.getpid())
    # 获取进程的父进程编号
    print("sing的父进程,", os.getppid())
    # 获取当前进程对象
    process_obj = multiprocessing.current_process()
    print("sing:", process_obj, 'name:', process_obj.name)
    for i in range(3):
        print("唱歌...")
        time.sleep(1)


def dance():
    # 获取子进程dance的编号
    print("dance,", os.getpid())
    # 获取进程的父进程编号
    print("dance的父进程,", os.getppid())
    # 获取当前进程对象
    process_obj = multiprocessing.current_process()
    print("dance:", process_obj, 'name:', process_obj.name)
    for i in range(3):
        print("跳舞...")
        time.sleep(1)


if __name__ == '__main__':
    # 获取主进程的编号
    print("main,", os.getpid())
    # 获取进程的父进程编号
    print("主进程的父进程,", os.getppid())
    # 获取当前进程对象
    process_obj = multiprocessing.current_process()
    print("main:", process_obj, 'name:', process_obj.name)


    # 2.创建进程对象
    #   target: 创建的进程对象所要执行的任务名(函数名)
    sing_process = multiprocessing.Process(target=sing)
    dance_process = multiprocessing.Process(target=dance)

    print("sing,",sing_process)
    print("dance,",dance_process)
    # 3.启动进程(创建子进程)
    sing_process.start()    # 真正创建sing的子进程
    dance_process.start()   # 真正创建dance的子进程


# 注意: 程序启动后,默认创建一个主进程, 多个进程运行是无序的
输出
主进程的父进程, 6636
main: <_MainProcess name='MainProcess' parent=None started> name: MainProcess
sing, <Process name='Process-1' parent=14447 initial>
dance, <Process name='Process-2' parent=14447 initial>
sing, 14449
sing的父进程, 14447
sing: <Process name='Process-1' parent=14447 started> name: Process-1
唱歌...
dance, 14450
dance的父进程, 14447
dance: <Process name='Process-2' parent=14447 started> name: Process-2
跳舞...
唱歌...
跳舞...
唱歌...
跳舞...

我们可以看出当创造对象的时候进程就有了名字对象父进程,且进入初始化状态,我们在启动之前发现状态已经变成要开始了

5.杀死进程 os.kill(进程的编号,9)

kill是给进程发信号,让进程自己杀死自己,9就是给进程发的信号。

kill有很多种信号,我们可以通过-l来了解大致有哪些

# 多任务:同一时间多个任务一起执行(唱歌的同时跳舞)

# 1.导入模块
# 2.创建进程对象
# 3.启动进程(创建子进程)

# 注意: 程序启动后,默认创建一个主进程, 多个进程运行是无序的
# target: 创建的进程对象所要执行的任务名(函数名)

# 进程对象.start() : 启动进程对象(创建子进程)


import time
# 1.导入模块
import multiprocessing
# 导入os模块
import os


def sing():
    # 获取子进程sing的编号
    print("sing,",os.getpid())
    # 获取进程的父进程编号
    print("sing的父进程,", os.getppid())
    # 获取当前进程对象
    process_obj = multiprocessing.current_process()
    print("sing:", process_obj, 'name:', process_obj.name)
    for i in range(3):
        print("唱歌...")
        time.sleep(1)
        os.kill(os.getpid(), 9)


def dance():
    # 获取子进程dance的编号
    print("dance,", os.getpid())
    # 获取进程的父进程编号
    print("dance的父进程,", os.getppid())
    # 获取当前进程对象
    process_obj = multiprocessing.current_process()
    print("dance:", process_obj, 'name:', process_obj.name)
    for i in range(3):
        print("跳舞...")
        time.sleep(1)


if __name__ == '__main__':
    # 获取主进程的编号
    print("main,", os.getpid())
    # 获取进程的父进程编号
    print("主进程的父进程,", os.getppid())
    # 获取当前进程对象
    process_obj = multiprocessing.current_process()
    print("main:", process_obj, 'name:', process_obj.name)


    # 2.创建进程对象
    #   target: 创建的进程对象所要执行的任务名(函数名)
    sing_process = multiprocessing.Process(target=sing)
    dance_process = multiprocessing.Process(target=dance)

    print("sing,",sing_process)
    print("dance,",dance_process)
    # 3.启动进程(创建子进程)
    sing_process.start()    # 真正创建sing的子进程
    dance_process.start()   # 真正创建dance的子进程


# 注意: 程序启动后,默认创建一个主进程, 多个进程运行是无序的

除了这个,我们平常在终端里执行程序时也可以通过ctrl+C杀死程序。

5.进程执行带有参数的任务

Process类执行任务并给任务传参数有两种方式:

  • args 表示以元组的方式给执行任务传参
  • kwargs 表示以字典方式给执行任务传参
import multiprocessing
import time


def sing(*args):
    for i in range(args[0]):
        print(f"{args[1]} is singing")
        time.sleep(0.2)


def dance(num,name):
    for i in range(num):
        print(f"{name} is dancing")
        time.sleep(0.5)


if __name__ == '__main__':
    process_sing = multiprocessing.Process(target=sing, args=(5, 'Koo'), name="process01")
    process_dance = multiprocessing.Process(target=dance, kwargs={'num': 5, 'name': 'Pee'}, name="process02")
    process_sing.start()
    process_dance.start()
输出
Koo is singing
Pee is dancing
Koo is singing
Koo is singing
Pee is dancing
Koo is singing
Koo is singing
Pee is dancing
Pee is dancing
Pee is dancing

6.进程的注意点

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

每个进程都是享有独立的内存资源,不互相共通,读取主进程的全局变量也只是相当于拷贝了主进程的全局变量,无论在子进程里如何改变变量,主进程也不会受到影响。

import multiprocessing
import time


my_list = []


def add_list():
    for i in range(5):
        my_list.append(i)
        print("add:", i)
        time.sleep(0.5)
    print("add_list:", my_list)


def read_list():
    print("my_list=", my_list)


add_process = multiprocessing.Process(target=add_list)
read_process = multiprocessing.Process(target=read_list)

if __name__ == '__main__':
    print(my_list)
    add_process.start()
    time.sleep(3)
		# 或者使用 add_process.join() 等于主进程会等待子进程add_procee执行完才会往下执行
    read_process.start()

输出

[]
add: 0
add: 1
add: 2
add: 3
add: 4
add_list: [0, 1, 2, 3, 4]
my_list= []
2.主进程会等子进程全部结束才会结束
import multiprocessing
import time


my_list = []


def add_list():
    for i in range(5):
        my_list.append(i)
        print("add:", i)
        time.sleep(0.5)
    print("add_list:", my_list)


def read_list():
    print("my_list=", my_list)


add_process = multiprocessing.Process(target=add_list)
read_process = multiprocessing.Process(target=read_list)

if __name__ == '__main__':
    print(my_list)
    add_process.start()
    time.sleep(0.5)
    print("主进程要结束")
    exit()

    read_process.start()
 输出
[]
add: 0
主进程要结束
add: 1
add: 2
add: 3
add: 4
add_list: [0, 1, 2, 3, 4]

可以看出主进程即使想退出,也要等待子进程结束再结束,如果我们想要主进程结束的时候,子进程必须要也跟着结束,有以下2种方法。

1.守护主进程

在创建子进程对象的时候,设置参数daemon = True

def add_list():
    for i in range(5):
        my_list.append(i)
        print("add:", i)
        time.sleep(0.5)
    print("add_list:", my_list)


def read_list():
    print("my_list=", my_list)


add_process = multiprocessing.Process(target=add_list, daemon=True)
read_process = multiprocessing.Process(target=read_list)

if __name__ == '__main__':
    print(my_list)
    add_process.start()
    time.sleep(0.5)
    print("主进程要结束")
    exit()

    read_process.start()

输出

[]
add: 0
主进程要结束
2.直接杀死子进程

有2种方法,一种是使用前面的os.kill命令来给子进程发送信号,让他自杀,但是需要导入os模块,一种是用多进程类里的方法也可以。

import multiprocessing
import time
import os

my_list = []


def add_list():
    for i in range(5):
        my_list.append(i)
        print("add:", i)
        time.sleep(0.5)
    print("add_list:", my_list)


def read_list():
    print("my_list=", my_list)


add_process = multiprocessing.Process(target=add_list) # 1. 设置参数 daemon = True 守护子进程
read_process = multiprocessing.Process(target=read_list)

if __name__ == '__main__':
    print(my_list)
    add_process.start()
    time.sleep(0.5)
    print("主进程要结束")
    # os.kill(add_process.pid, 9) # 2.os.kill(pid,9) 直接杀死子进程 但是需要导入os模块
    add_process.terminate() # 3. 不需要导入模块也能让子进程自杀
    exit()

    read_process.start()

3.2.3 线程

1.介绍

线程也是Python中实行多任务的另外一种方式

2.概念

线程是进程执行代码里的一个分支,每个线程要执行需要cpu进行调度,线程是cpu调度的单位,每个进程里至少有一个线程,这个线程一般是我们说的主线程。

3.多线程的使用

和进程类似,只是导入的模块不同。

import threading
import time

# 唱歌任务
def sing():
    # 扩展: 获取当前线程
    # print("sing当前执行的线程为:", threading.current_thread())
    for i in range(3):
        print("正在唱歌...%d" % i)
        time.sleep(1)

# 跳舞任务
def dance():
    # 扩展: 获取当前线程
    # print("dance当前执行的线程为:", threading.current_thread())
    for i in range(3):
        print("正在跳舞...%d" % i)
        time.sleep(1)


if __name__ == '__main__':
    # 扩展: 获取当前线程
    # print("当前执行的线程为:", threading.current_thread())
    # 创建唱歌的线程
    # target: 线程执行的函数名
    sing_thread = threading.Thread(target=sing)

    # 创建跳舞的线程
    dance_thread = threading.Thread(target=dance)

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

同样的,多线程执行也是无序的

4.多线程执行带传参的任务

import threading
import time


# 带有参数的任务
def task(count):
    for i in range(count):
        print("任务执行中..")
        time.sleep(0.2)
    else:
        print("任务执行完成")


if __name__ == '__main__':
    # 创建子线程
    # args: 以元组的方式给任务传入参数
    sub_thread = threading.Thread(target=task, args=(5,))
    sub_thread.start()

5.多线程的注意点

  1. 线程之间的执行是无序的,而且没有对应的pid
  2. 主线程要等待子线程结束后再结束
  3. 线程之间共享全局变量
  4. 线程之间共享全局变量可能导致的数据出错
1.线程和进程的无序
  • 线程之间执行是无序的,它是由cpu调度决定的 ,cpu调度哪个线程,哪个线程就先执行,没有调度的线程不能执行。
  • 进程之间执行也是无序的,它是由操作系统调度决定的,操作系统调度哪个进程,哪个进程就先执行,没有调度的进程不能执行。
2.守护主线程

线程是没有编号的,我们要想主进程结束的时候直接杀死子进程只能通过设置daemon = True来实现。

有3种设置方式,但是都要在子进程开始之前实现.

sub_thread = threading.Thread(target=show_info, daemon=True)
sub_thread.setDaemon(True)
sub_thread.daemon=True
3.线程之间共享变量

和进程不同,线程之间是共享变量的。因为进程是资源分配的单位,而线程是cpu调度的单位,线程共用进程里的资源。

import threading
import time

po_list=[]

def add_list(*args):
    for i in args:
        po_list.append(i)
        print(f"{i}加入队伍中")


def del_list(*args):
    for i in args:
        po_list.remove(i)
        print(f"{i}移出了队伍")


if __name__ == '__main__':
    add_thread = threading.Thread(target=add_list, args =('tom', 'kitty', 'jerry', 'lolo'))
    del_thread = threading.Thread(target=del_list, args=('tom', 'kitty', 'jerry', 'lolo'))
    add_thread.start()
    del_thread.start()
    time.sleep(360)
    print("END")
4.线程之间的共享变量可能导致资源争抢出错

数据量越大,程序执行时间越久,越容易出错。线程之前会互相打断。

import threading
import time

po = 0


def add_list():
    global po
    for i in range(1000000):
        po += i
        print(f"{po}")


def del_list():
    global po
    for i in range(1000000):
        po -= i
        print(f"{po}")


if __name__ == '__main__':
    add_thread = threading.Thread(target=add_list)
    del_thread = threading.Thread(target=del_list)
    add_thread.start()
    del_thread.start()
    time.sleep(360)
    print("END")
输出
4213
7468219543
7467884872
7467550200
7467215527

7467571815
74679281047467593430
7467258755
7466924079

6.解决线程资源争抢的方法

全局变量数据错误的解决办法:

线程同步: 保证同一时刻只能有一个线程去操作全局变量 同步: 就是协同步调,按预定的先后次序进行运行。如:你说完,我再说, 好比现实生活中的对讲机

1.线程等待(join)
import threading

# 定义全局变量
g_num = 0


# 循环1000000次每次给全局变量加1
def sum_num1():
    for i in range(1000000):
        global g_num
        g_num += 1

    print("sum1:", g_num)


# 循环1000000次每次给全局变量加1
def sum_num2():
    for i in range(1000000):
        global g_num
        g_num += 1
    print("sum2:", g_num)


if __name__ == '__main__':
    # 创建两个线程
    first_thread = threading.Thread(target=sum_num1)
    second_thread = threading.Thread(target=sum_num2)

    # 启动线程
    first_thread.start()
    # 主线程等待第一个线程执行完成以后代码再继续执行,让其执行第二个线程
    # 线程同步: 一个任务执行完成以后另外一个任务才能执行,同一个时刻只有一个任务在执行
    first_thread.join()
    # 启动线程
    second_thread.start()
2.互斥锁 threading.Lock()

线程等待实际上,是变相让cpu执行变成了单线程,等于变相取消了多任务,这样会使程序的执行效率变得低下,我们还有一个互斥锁的功能,来对共享数据进行锁定,保证同一时间,只有一个线程会操作到,其他线程必须等到这个线程执行完毕 解锁后才能再度争抢使用。

使用格式

# 创建锁
mutex = threading.Lock()

# 上锁
mutex.acquire()

...这里编写代码能保证同一时刻只能有一个线程去操作, 对共享数据进行锁定...

# 释放锁
mutex.release()

注意点:

  • acquire和release方法之间的代码同一时刻只能有一个线程去操作
  • 如果在调用acquire方法的时候 其他线程已经使用了这个互斥锁,那么此时acquire方法会堵塞,直到这个互斥锁释放后才能再次上锁。
  • 锁一个进程里只能有一把

代码示例

import threading
import time

po = 0
mutex = threading.Lock()

def add_list():
    global po
    for i in range(1000000):
        mutex.acquire()
        po += 1
        mutex.release()
    print(f"sum1:{po}")


def del_list():
    global po
    for i in range(1000000):
        mutex.acquire()
        po -= 1
        mutex.release()
    print(f"sum2:{po}")


if __name__ == '__main__':
    add_thread = threading.Thread(target=add_list)
    del_thread = threading.Thread(target=del_list)
    add_thread.start()
    del_thread.start()
    time.sleep(60)
    print("END")
输出
sum1:139505
sum2:0
END

可以发现执行效率也变慢了不少,因为同一时刻操作变量po的只能有一个。

不过锁还是比线程等待好,因为它只锁定一部分执行代码,其他线程只是无法执行这段代码。

互斥锁如果没锁好会变成死锁。

3.死锁

如果锁释放的位置不正确,会导致其他线程一直在等待锁的释放,从而造成死锁。

mport threading
import time

# 创建互斥锁
lock = threading.Lock()


# 根据下标去取值, 保证同一时刻只能有一个线程去取值
def get_value(index):

    # 上锁
    lock.acquire()
    print(threading.current_thread())
    my_list = [3,6,8,1]
    # 判断下标释放越界
    if index >= len(my_list):
        print("下标越界:", index)
        return
    value = my_list[index]
    print(value)
    time.sleep(0.2)
    # 释放锁
    lock.release()


if __name__ == '__main__':
    # 模拟大量线程去执行取值操作
    for i in range(30):
        sub_thread = threading.Thread(target=get_value, args=(i,))
        sub_thread.start()

可以发现第四个线程第五开始因为进入if判断return后没有解锁,会导致其他线程全部被卡死。

解决的方法是只要在return前释放锁。

3.2.4 进程和线程的区别和关系

1. 关系

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

2. 区别

  • 进程之前不共享全局变量
  • 线程之间共享全局变量,但是也要注意资源争抢问题,线程共享的是进程分配到的资源
  • 创建进程的开销大,线程开销小
  • 进程是系统资源分配的单位,线程是cpu调度的单位
  • 线程无法独立工作,必须依附在进程上
  • 多进程开发比单进程多线程稳定

3.优缺点

进程:

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

线程:

  • 优点:资源开销小
  • 缺点:不能使用多核

因为Python是解释性语言,每一行代码都要逐行解释执行,而cpu调度资源每个核只能调度一个解释器,每个核执行一个进程,进程里赋予一个解释器,而线程要执行都要靠一个进程里的解释器。所以实际上只能单核跑线程。

3.2.5 拓展:协程

1. 什么是协程

又称为微线程,是Python拥有的实现多任务的方法,是比线程占用资源更小的执行单位。

在一个线程中,我们可以执行其中某个函数,然后在函数结束之前保存一些临时变量,切换到同一个线程里的其他函数开始执行代码,在同一线程内切换执行由开发者自己确定。

2. 协程的优点

协程的执行效率极高,因为函数的切换不是线程切换,而是有程序自己控制的,没有线程切换的开销。协程也不需要多线程的锁机制,因为只要一个线程,不存在变量争夺,控制共享资源也不要上锁,只需要判断状态就好。

3.Python自带的协程示例

import time


def sing():
    while True:
        print("sing")
        # 可以暂停或者继续函数
        yield


def dance():
    while True:
        print("dance")
        yield


if __name__ == '__main__':
    sing_x = sing()
    dance_x = dance()
    while True:
        next(sing_x)
        next(dance_x)
        time.sleep(1)

4.gevent库

gevent 是一个第三方库。

Python中仅提供了对协程的基本支持,但是不完全。而第三方的gevent为Python提供了比较完善的协程支持。

其原理是当一个任务函数遇到IO(指的是input output 输入输出,比如网络、文件操作等)操作时,比如访问网络,就自动切换到其他的任务函数执行,等到IO操作完成,再在适当的时候切换回来继续执行。

由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有任务函数在运行,而不是等待IO,得以实现多任务,提高程序执行效率。

import gevent
import time


def sing(count):
    for i in range(count):
        print("singing...")
        time.sleep(1)
        # 不认系统自带的sleep,无法理解为io耗时,不会切换到dance


def dance(count):
    for i in range(count):
        print("dancing")


if __name__ == '__main__':
    g1 = gevent.spawn(sing, 5)
    g2 = gevent.spawn(dance, 5)
    # gevent创建的协程,只有遇到io耗时才会切换执行
    g1.join()
    # 主线程等待协程g1执行完毕再继续 这里遇到了gevent锁理解的耗时操作 开始切换到g1开始执行
    g2.join()
    # 主线程会等待g1 g2 执行完才会结束

gevent只认自己库里的睡眠,所以这套我们执行完毕会发现压根没有在g1g2之间切换。

我们可以使用gevent库里自己的睡眠来创造io耗时

import gevent


def sing(count):
    for i in range(count):
        print("singing...")
        gevent.sleep(1)

def dance(count):
    for i in range(count):
        print("dancing")
        gevent.sleep(1)


if __name__ == '__main__':
    g1 = gevent.spawn(sing, 5)
    g2 = gevent.spawn(dance, 5)
    # gevent创建的协程,只有遇到io耗时才会切换执行
    g1.join()
    g2.join()
    # 主线程会等待g1 g2 执行完才会结束

5. 给程序打补丁

import gevent
import time
from gevent import monkey

monkey.patch_all()


def sing(count):
    for i in range(count):
        print("singing...")
        time.sleep(1)


def dance(count):
    for i in range(count):
        print("dancing")
        time.sleep(1)


if __name__ == '__main__':
    g1 = gevent.spawn(sing, 5)
    g2 = gevent.spawn(dance, 5)
    # gevent创建的协程,只有遇到io耗时才会切换执行
    g1.join()
    g2.join()
    # 主线程会等待g1 g2 执行完才会结束

我们可以查看一下monkey.patch_all()是什么

会发现它把所有系统的输出输入包括sleep都变成True

多任务损失Python实现 python多任务编程_父进程

拓展

gevent.joinall([想要启动的协程])