这是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('进程结束')
- 特点
- 按照你规定的进程池中进程的个数将任务全部加载进去(不用等待其他任务执行完成),而一旦有任务执行完成,进程便会自动加载下一个任务,若没有任务可执行则进程处于空闲状态
- 非阻塞式的进程池依靠主进程的调用,也就是如果主进程挂了,进程池的任务就不会执行,因此我们得采用pool.close和pool.join来进行对主函数的调阻碍
- 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('进程结束')
- 特点
- 添加一个任务执行一个任务,如果一个任务不结束另一个任务进不来
- 其他的特点和上述的非阻塞式进程相似
进程间的通讯
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() 让主进程让步,直至进程池结束才让主进程进行下面的动作