python进程与线程--多进程

1,180 阅读7分钟

这是sylu计算机协会第一次技术征文活动

进程与线程

概念

  • 认识

在生活中,你可能一边吃饭一边看视频,一边做作业一边听音乐......这些都是生活中的多任务场景。电脑也可以执行多任务,比如你可以打开浏览器的同时听歌......简单的来说多任务就是同一时间内运行多个程序

  • 单核

单核CPU实现多任务的原理:操作系统轮流让各人人物交替执行,QQ执行2us,切换到微信执行2us,再切换到酷狗音乐2us…….。表面看上去,每个任务反复执行,但实际上CPU执行太快了,所以给人的感觉就是同时在执行

  • 多核

多核CPU实现任务原理:真正的多核执行多个任务只能在多核CPU上实现,但是由于任务数量远远多于CPU的核心数量,所以操作系统会自动把很多任务自动调度到核心上

  • 并发

当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的进程,它只能把CPU运行时间划分为若干个时间段,再将时间段分配给各个线程执行,在一个时间段的线程代码运行时,其他线程处于挂起状态,这种方式我们称之为并发

  • 并行

当系统有一个以上的CPU时,则线程的操作有可能非并发。当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行

  • 实现多任务的方式

    • 多进程模式
    • 多线程模式
    • 协程

进程 >> 线程 >> 协程

多进程

定义

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。(来源:百度百科)

我们简单的来理解一下:对于操作系统来说,一个任务就是一个进程,打个比方说打开浏览器就是启动一个浏览器的进程,在打开一个记事本就是一个记事本的进程,如果打开两个记事本就是启动两个记事本进程

  • 优点
    • 稳定性高,一个进程崩溃了,不会影响其他进程
  • 缺点
    • 创建进程开销巨大
    • 操作系统能同时运行进程数目有限

创建进程

在linux下可以使用fork函数创建进程,在windows系统上可以引multiprocessing模块创建进程。我们可以使用multiprocessing模块中的Process类创建新的进程

Process类说明

方法名称参数功能
__ init __()name:进程名称
args:任意位置参数(给函数传递的参数),可迭代的
kwargs:任意关键字参数
target:进程实例所调动的对象(你所想调用的子进程动作)
group:一般用不到
构造方法
start()启动进程
terminate()结束进程
join()timeout:等等秒数,可选是否等等进程执行结束
run()如果没有给定target参数,对这个对象调用start()方法时,就将执行对象中的run()方法
is_alive()判断进程实例是否在执行
常用属性说明
name进程名称
pid当前进程的PID
  • 实例
import os
from multiprocessing import Process
from time import sleep
def task1(s):
    while True:
    	sleep(s)
    	print('这是任务1。。。。。。','os.getpid()'----'os.gerppdi()')
def task2(s):
    while True:
        sleep(s)
        print('这是任务2。。。。。。','os.getpid()'----'os.gerppdi()')
if __name__ == '__main__':
    #子进程
    p1 = Process(target=task1,name='task1',args=(1,))
    p1.start()
    p2 = Process(target=task2,name='task2',args=(2,))
    p2.start()
# 我们通过主进程的即使来控制子进程的停止
number = 0
while True:
    number += 1
    sleep(0.2)
    if number == 100:
        p1.terminate()
        p2.terminate()
        break
    else :
        print('------>'number)
print('子进程已结束')        
  • 全局变量:在这里定义的全局变量在task1和task2中同步运行互不干扰(无论是可变还是不可变)

自定义进程

from multiprocessing import Process

class Myprocess(Process):
    #对run方法重写
    def run(self):
        n=1
        while True:
            print('{}------>自定义进程,n:{}'.format(self.name,n))
            n += 1
if __name__ == '__main__':
    p1 = Myprocess(name='小明')
    p1.start()
    p2 = Myprocess(name='小美')
    p2.start()

进程池

当需要创建的子进程数量不多时,可以直接利用multiprocessing中的Process动态生成多个进程,但是如果是成百上千个目标,手动的去创建进程的工作量巨大,此时就可以用到multiprocessing模块提供的Pool方法。初始化Pool时,可以指定一个最大进程数,当有新的请求提交到Pool中时,如果池还没有满,那么就会创建一个新的进程来执行该请求,但是如果池中的进程数已达到最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程来执行

  • 非阻塞进程
import os
import time
from multiprocessing import Pool
from random import random
def task(task_name):
    print('开始做任务啦',task_name)
    start = time.time()
    time.sleep(random() * 2)
    end = time.time()
	return '完成任务:{},用时:{},进程id{}'.format(task_name,(end - start),os.getpid())

result = []
def callback_func(n):
    result.append(n)
    
if '__name__' == '__main__':
    pool = Pool(5)
    tasks = ['听音乐','吃饭','洗衣服','睡觉','打游戏','散步','做饭']
    for task1 in tasks:
        pool.apply_async(task,args=(task1,),callback = callback_func)
    pool.close() # 添加任务结束
    pool.join()  # 阻碍主进程结束
print('进程结束')    
  • 特点
  1. 按照你规定的进程池中进程的个数将任务全部加载进去(不用等待其他任务执行完成),而一旦有任务执行完成,进程便会自动加载下一个任务,若没有任务可执行则进程处于空闲状态
  2. 非阻塞式的进程池依靠主进程的调用,也就是如果主进程挂了,进程池的任务就不会执行,因此我们得采用pool.close和pool.join来进行对主函数的调阻碍
  3. callback函数为进程池中的回调函数,我们在进程之外定义一个回调函数,回调函数会接受任务函数的返回值然后执行回调函数内部的动作,这里的回调函数并不是等带所有任务完成再去回调,而是单个任务执行完成就回调对应的任务返回值
  • 阻塞进程

阻塞进程和非阻塞进程的用法区别就在于apply和apply_async的区别

import os
import time
from multiprocessing import Pool
from random import random
def task(task_name):
    print('开始做任务啦',task_name)
    start = time.time()
    time.sleep(random() * 2)
    end = time.time()
	return '完成任务:{},用时:{},进程id{}'.format(task_name,(end - start),os.getpid())

result = []
def callback_func(n):
    result.append(n)
    
if '__name__' == '__main__':
    pool = Pool(5)
    tasks = ['听音乐','吃饭','洗衣服','睡觉','打游戏','散步','做饭']
    for task1 in tasks:
        pool.apply(task,args=(task1,),callback = callback_func)
    pool.close() # 添加任务结束
    pool.join()  # 阻碍主进程结束
print('进程结束')    
  • 特点
  1. 添加一个任务执行一个任务,如果一个任务不结束另一个任务进不来
  2. 其他的特点和上述的非阻塞式进程相似

进程间的通讯

from multiprocessing import Queue
q = Queue(5)   #设置队列里的最大元素
#向队列添加值
q.put('a')
q.put('b')
q.put('c')
q.put('d')
q.put('e')  #如果队列已满再往内部添加元素就会阻塞,知道内部有元素被拿走才会继续添加
if not q.full():  #q.full()检测队列是否满了,q.empty()检测队列是否为空
	q.put('f')
else:
    print('队列已满')
#获取队列的值    
print(q.get())
print(q.get())
print(q.get())
print(q.get())
print(q.get())
print(q.get()) #与上述一样取完值以后变空也会阻塞
  • 语法
q.put(block=True,timeout=None)
q.get(block=True,timeout=None)
参数意义
block是否阻止阻塞,默认为阻止
timeout超时,默认为None(一直),可以自己修改时间
from multiprocessing Process,Queue
from time import sleep

def download(q):
    images = ['girl.jpg','boy.jpg','man.jpg']
    for image in images:
        print('正在下载:',image)
        sleep(1)
        q.put(image)
def getfile(q):
    while True:
        try:
            file = q.get(timeout=5)
            print('文件保存成功!'.format(file))
        except:
            print('全部保存完毕!')
            break
if __name__ == '__main__':
    q = Queue(5)
    p1 = Process(target=download,args=(q,))
    p2 = Process(target=getfile,args=(q,))
    p1.start()
    p1.join()
    
    p2.start()
    p2.join()
    
    print('进程结束')
  • 我们怎么能让两个进程实现通讯呢?

在这里我们通过定义一个队列q作为参数传到两个进程的函数中,这样就能实现两个函数相互通讯了

小结

进程池:

pool = Pool(nax) 创建进程池对象

pool.apply() 阻塞的

pool.apply_async() 非阻塞的

pool.close() 添加任务结束

pool.join() 让主进程让步,直至进程池结束才让主进程进行下面的动作