多任务编程

先来看三个问题,对于多任务有个简单的认识: 

1.首先什么是多任务呢?

就是操作系统可以同时运⾏多个任务。打个 ⽐⽅,你⼀边在⽤浏览器上⽹,⼀边在听MP3,⼀边在⽤Word赶作业,这就是多任务,⾄少同时有3个任务正在运⾏。还有很多任务悄悄地在后台同时运⾏着,只是桌⾯上没有显示⽽已。

2.那么单核CPU该如何实现“多任务”呢? 

操作系统轮流让各个任务交替执⾏,每个任务执⾏0.01秒,这样反复执⾏下去。 表⾯上看,每个任务交替执⾏,但CPU的执⾏速度实在是太快了,感觉就像所有任务都在同时执⾏⼀样。正如下图所示:

python apscheduler 多任务 python多任务编程_进程池

3.多核CPU又是如何实现“多任务”的呢?

真正的并⾏执⾏多任务只能在多核CPU上实现,但是,由于任务数量远远多于CPU的核⼼数量,所以,操作系统也会⾃动把很多任务轮流调度到每个核⼼上执⾏。如下图所示:

python apscheduler 多任务 python多任务编程_子进程_02

 多任务编程分为多进程编程、多线程编程和协程。


多进程编程

进程的创建 

程序和进程它们有什么区别呢?

程序:编写完毕的代码,在没有运⾏的时候,称之为程序

进程:正在运⾏着的代码,就成为进程  

注意:进程,除了包含代码以外,还有需要运⾏的环境等,所以和程序是有区别的。

进程的五状态模型 

python apscheduler 多任务 python多任务编程_进程池_03

 

 创建子进程

Python的os模块封装了常⻅的系统调⽤,其中就包括fork,可以在Python程序中轻松创建⼦进程: 

python apscheduler 多任务 python多任务编程_多任务编程_04

 

在OS模块中,创建子进程时常用到的函数有:

  • os.fork( )
  • os.getpid( )   获取当前进程的pid  (process id)
  • os.getppid( )   获取当前进程的父进程pid (parent process id)

需要注意的是: 

1.执⾏到os.fork()时,操作系统会创建⼀个新的进程复制⽗进程的所有信息到⼦进程中。 

2.普通的函数调⽤,调⽤⼀次,返回⼀次,但是fork()调⽤⼀次,返回两次。

3.⽗进程和⼦进程都会从fork()函数中得到⼀个返回值,⼦进程返回是0,⽽⽗进程中返回⼦进程的 id号。

4.多进程中,每个进程中所有数据(包括全局变量)都各有拥有⼀份,所以遇到多进程修改全局变量的情况时互不影响。

下面我们用具体的代码,看一下如何创建子进程,以及通过输出结果观察上面需要注意的点,代码如下:

import os
import time

# 定义一个全局变量money
money = 100
print("当前进程的pid:", os.getpid())
print("当前进程的父进程pid:", os.getppid())

p = os.fork()
# 子进程返回的是0
if p == 0:
    money = 200
    print("子进程返回的信息, money=%d" %(money))
# 父进程返回的是子进程的pid
else:
    print("创建子进程%s, 父进程是%d" %(p,  os.getppid()))
    print(money)

输出结果如下:

python apscheduler 多任务 python多任务编程_多任务编程_05

注意,fork函数,只在Unix/Linux/Mac上运⾏,windows不可以 。 

多进程编程 

Windows没有fork调⽤,由于Python是跨平台的,所以有一个 multiprocessing模块就是跨平台版本的多进程模块。multiprocessing模块提供了⼀个Process类来代表⼀个进程对象。 

Process类需要传递的参数

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

  • target:表示这个进程实例所调⽤对象;     
  • args:表示调⽤对象的位置参数元组;     
  • kwargs:表示调⽤对象的关键字参数字典;     
  • name:为当前进程实例的别名;     
  • group:⼤多数情况下⽤不到;

Process类常⽤⽅法:   

  •  is_alive():    判断进程实例是否还在执⾏;   
  •  join([timeout]):    是否等待进程实例执⾏结束,或等待多少秒;     
  • start():     启动进程实例(创建⼦进程);     
  • run():     如果没有给定target参数,对这个对象调⽤start()⽅法时,就将执 ⾏对象中的run()⽅法;     
  • terminate():    不管任务是否完成,⽴即终⽌; 

Process类常⽤属性:     

  • name:当前进程实例别名,默认Process-N,N为从1开始计数;     
  • pid:当前进程实例的PID值; 

下面通过代码看一下多任务编程的两个方法 :

多进程编程方法1: 实例化对象

 通过实例化对象的方法创建子进程,具体代码如下:

from multiprocessing import Process
import time

def task1():
    print("正在听音乐")
    time.sleep(1)

def task2():
    print("正在编程......")
    time.sleep(0.5)

def no_multi():
    task1()
    task2()

def use_multi():
    p1 = Process(target=task1)
    p2 = Process(target=task2)
    p1.start()
    p2.start()
    p1.join()
    p2.join()
    # p.join()  阻塞当前进程, 当p1.start()之后, p1就提示主进程, 需要等待p1进程执行结束才能向下执行, 那么主进程就乖乖等着, 自然不会执行p2.start()

if __name__ == '__main__':
    # 主进程
    start_time= time.time()
    no_multi()
    end_time = time.time()
    print(end_time-start_time)

    start_time = time.time()
    use_multi()
    end_time = time.time()
    print(end_time - start_time)

代码里面的no_multi函数是没有使用多进程的函数,use_multi函数是通过实例化方法创建了两个子进程,通过结果,可以看到多进程运行时间比没有使用多进程运行时间短,结果如下:

python apscheduler 多任务 python多任务编程_多任务编程_06

多进程编程方法2: 创建子类

通过创建子类,重写run方法,实现多进程编程,具体例子代码如下:

 

"""
创建子类, 继承的方式
"""
from multiprocessing import  Process
import time
class MyProcess(Process):
    """
    创建自己的进程, 父类是Process
    """
    def __init__(self, music_name):
        super(MyProcess, self).__init__()
        self.music_name = music_name

    def run(self):
        """重写run方法, 内容是你要执行的任务"""
        print("听音乐%s" %(self.music_name))
        time.sleep(1)

# 开启进程: p.start()  ====== p.run()
if __name__ == '__main__':
    for i in range(10):
        p = MyProcess("音乐%d" %(i))
        p.start()

输出结果如下:

python apscheduler 多任务 python多任务编程_子进程_07

进程池 

为什么需要进程池Pool呢?

1.当被操作对象数目不大时,可以直接利用multiprocessing中的Process动态成生多个进程,十几个还好,但如果是上百个,上千个目标,手动的去限制进程数量却又太过繁琐,此时可以发挥进程池的功效。

2.Pool可以提供指定数量的进程供用户调用,当有新的请求提交到pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到规定最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程来它。 

它的工作原理如下图所示:

python apscheduler 多任务 python多任务编程_进程池_08

 

 Python中的multiprocessing模块中的Pool就是进程池,下面通过具体例子写一个进程池的代码:

def is_prime(num):
    """判断素数"""
    if num == 1:
        return False
    for i in range(2, num):
        if num % i == 0:
            return False
    else:
        return True

def task(num):
    if is_prime(num):
        print("%d是素数" % (num))

from multiprocessing import Process

# 判断1-100000之间所有的素数
def use_mutli():
    ps = []
    # 不要开启太多进程, 创建子进程会耗费时间和空间(内存);
    for num in range(1, 10000):
        # 实例化子进程对象
        p = Process(target=task, args=(num,))
        # 开启子进程
        p.start()
        # 存储所有的子进程对象
        ps.append(p)
    # 阻塞子进程, 等待所有的子进程执行结束, 再执行主进程;
    [p.join() for p in ps]

# 判断1-100000之间所有的素数
def no_mutli():
    for num in range(1, 100000):
        task(num)

def use_pool():
    """使用进程池"""
    from multiprocessing import Pool
    from multiprocessing import  cpu_count  # 4个
    p = Pool(cpu_count())
    p.map(task, list(range(1, 100000)))
    p.close()  # 关闭进程池
    p.join()  # 阻塞, 等待所有的子进程执行结束, 再执行主进程;

if __name__ == '__main__':
    import time
    start_time = time.time()
    use_pool()      
    end_time = time.time()
    print(end_time - start_time)

使用进程池运行程序,可以看到输出结果的运行时间:

python apscheduler 多任务 python多任务编程_多任务编程_09

当不使用进程池,也不是用多任务时,很明显运行时间会比较长一点:

python apscheduler 多任务 python多任务编程_进程间通信_10

由于使用多进程时,需要开启上万个进程,这样会更加浪费时间。

进程间通信 

目的:

python apscheduler 多任务 python多任务编程_子进程_11

 

方式:

python apscheduler 多任务 python多任务编程_进程间通信_12

消息队列:

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

下面看一下Queue有哪些方法?

  • Queue.qsize():    返回当前队列包含的消息数量;
  • Queue.empty():    如果队列为空,返回True,反之False ;
  • Queue.full():    如果队列满了,返回True,反之False; 
  • Queue.get([block[, timeout]]):    获取队列中的⼀条消息,然后将其从列队中移除,block默认值为True;
  • Queue.get_nowait():     相当Queue.get(False);
  • Queue.put(item,[block[, timeout]]):     将item消息写⼊队列,block默认值 为True;
  • Queue.put_nowait(item):     相当Queue.put(item, False)

下面用代码实现消息队列,代码如下:

from multiprocessing import Process, Queue
import time
class Producer(Process):
    def __init__(self, queue):
        super(Producer, self).__init__()
        self.queue = queue
    def run(self):
        for i in range(10):
            self.queue.put(i)
            time.sleep(0.1)
            print("传递消息,内容为%s" %(i))

class Consumer(Process):
    def __init__(self, queue):
        super(Consumer, self).__init__()
        self.queue = queue
    def run(self):
        while True:
            time.sleep(0.1)
            recvDate = self.queue.get()
            print("接收到另一进程传递的数据:%s" %(recvDate))

if __name__ == '__main__':
    q = Queue()
    p1 = Producer(q)
    c1 = Consumer(q)

    p1.start()
    c1.start()
    p1.join()
    c1.join()

 输出结果为:

python apscheduler 多任务 python多任务编程_多任务编程_13