一.操作系统
- 为什么要有操作系统?
计算机是由一个或者多个处理器CPU,内存条,磁盘,键盘,鼠标,显示器,以及各种其他输入输出设备组成的机器。如果我们想让自己的应用程序运行在计算机上,我们需要了解
计算机中所有的细节例如处理器怎么运行,内存的分配等等。每位程序员不可能掌握所有系统实现的细节,并且管理优化这些部件是一件挑战性极强的工作。因此就出现了操作系统(操作系统也是一个软件)。
操作系统的定义:操作系统是一个用来协调、管理和控制计算机硬件和软件资源的系统程序,它位于硬件和应用程序之间。起承上启下的作用。我们写好的程序只需要给操作系统即可,
操作系统会给我们的程序分配内存等等一些操作。
二.多任务
多任务就是同一时刻多个任务同时执行,例如开演唱会时明星一边唱歌一边跳舞,开车时眼睛看路手操作方向盘。这些都是多任务场景。
对于电脑来说多任务就是同时运行多个应用程序,例如qq、微信、浏览器等等同时在电脑上运行。
- 电脑实现多任务的原理
例如qq、微信、网易云音乐播放器3个应用程序能同时运行是因为CPU在多个应用程序之间高速切换的结果,当CPU切换到了qq,就用0.01s时间(时间不确定)执行qq程序,然后再随机切换到其他应用程序在执行一段时间,CPU在多个程序之间快速往复执行,我们的肉眼根本感觉不到卡顿,导致我们的错觉感觉是同时运行的效果。如果电脑运行了多个程序有时候会出现卡顿现象是因为cup切换不过来了。
2.单核、双核CPU介绍:
单核CPU 指的是CPU中有一个核心(形象理解CPU是人的头,核心是头里面包含的大脑),用来处理程序。
双核/四核CPU 就是CPU中有2个或者4个核心,(1个脑袋中长了2个大脑或者4个大脑),相当于有2个单核CPU或者是4个单核CPU
3.查看CPU:
电脑-->属性-->设备管理器-->处理器,有4个表示一个CPU中有4个核心。
2.30GHz 表示运算速度,越高越好。例如1s中可以计算多少次。
4.在python中实现多任务有3种方式,进程、线程、协程。
三.进程
-
什么是进程
我们想通过酷我听歌,具体的过程应该是先找到酷我应用程序,然后双击就会播放音乐。
当我们双击的时候,操作系统将程序装载到内存中,操作系统为它分配资源,然后才能运行。运行起来的应用程序就称之为进程。也就是说当程序不运行的时候我们称之为程序,当程序运行起来他就是一个进程。通俗的理解就是不运行的时候是程序,运行起来就是进程。程序和进程的对应关系是:程序只有一个,但是进程可以有多个。
例如: 我们可以打开多个QQ
2.创建多进程
- 不使用多进程实现控制台先打印唱歌,再打印跳舞
import time
def sing():
# 唱歌3秒
for i in range(3):
print('正在唱歌。。。')
time.sleep(1)
def dance():
# 跳舞3秒
for i in range(3):
print('正在跳舞。。。')
time.sleep(1)
if __name__ == '__main__': # 程序执行的入口
sing()
dance()
程序运行花费了6s 的时间
- 使用进程让唱歌跳舞一起执行
import time
import multiprocessing
def sing():
# 唱歌3秒
for i in range(3):
print('正在唱歌。。。')
time.sleep(1)
def dance():
# 跳舞3秒
for i in range(3):
print('正在跳舞。。。')
time.sleep(1)
def main():
m1 = multiprocessing.Process(target=sing) # 创建m1 进程,执行sing()函数
m2 = multiprocessing.Process(target=dance) # 创建m2 进程,执行dance()函数
m1.start() # 开始执行进程m1
m2.start() # 开始执行进程m2
# 开启了两个子进程,m1和m2
if __name__ == '__main__': # 程序执行的入口
main()
运行结果: 正在唱歌。。。
正在跳舞。。。
正在唱歌。。。
正在跳舞。。。
正在唱歌。。。
正在跳舞。。。
花了3s时间,提高了程序的运行效率
程序理解:
主进程从main()开始执行,执行main函数体,当执行到p1.start()时,创建一个子进程,p1子进程中的代码和主进程相同,只是程序执行的开始是 sing函数体。
主进程执行到p2.start()时,同样复制一份主进程代码从danc函数体开始执行。
使用进程方式实现多任务耗费的资源比较大,因为一个进程就需要使用一份系统资源。
3.进程的状态
在程序运行的过程中,由于被操作系统的调度算法控制,程序会进入几个状态:就绪,运行和阻塞。
(1)就绪状态
当进程已分配到除CPU以外的所有必要的资源,只要获得处理机便可立即执行,这时的进程状态称为就绪状态。
(2)执行/运行(Running)状态
当进程已获得处理机,其程序正在处理机上执行,此时的进程状态称为执行状态。
(3)阻塞(Blocked)状态
正在执行的进程,由于等待某个事件发生而无法执行时,便放弃处理机而处于阻塞状态。引起进程阻塞的事件可有多种,例如,等待I/O完成、申请缓冲区不能满足、等待信件(信号)等。
4. 进程之间的 通讯
刚才我们说了进程可以理解为复制了一份程序有加载到了内存了,进程之间是独立的,如果我想两个进程之间进行通讯怎么办呢?我们可以使用Queue 队列,队列是一种先进先出的存储数据结构,就比如排队上厕所一个道理。
两个进程通讯,就是一个子进程往queue中写内容,另一个进程从queue中取出数据。就实现了进程间的通讯了。
queue队列
(1)q = multiprocessing.Queue(3) # 3表示只能存放3个数据
参数 :maxsize是队列中允许的最大项数。如果省略此参数,则无大小限制。
返回值q 是队列对象
(2)put()方法 ,向队列中存放数据。如果队列已满,此方法将阻塞至有空间可用为止。
(3)get()返回q中的一个项目。如果q为空,此方法将阻塞,直到队列中有项目可用为止。
(4)get_nowait(): 不等待,直接抛出异常
(5)full()如果q已满,返回为True
(6)q.empty() 如果调用此方法时 q为空,返回True。
put()和get()
get_nowait()
full()
empty()
练习1:使用queue模拟多任务下载和处理数据
from multiprocessing import Process, Queue
def download_data(q):
lst = ['a', 'b', 'c']
for i in lst:
q.put(i) # 将下载的数据保存到队列中
print('数据下载完了。。。')
def process_data(q):
for i in range(q.qsize()):
print(q.get())
print('数据处理完了。。。')
def main():
q = Queue() # 创建一个队列
# 创建两个子进程
q1 = Process(target=download_data, args=(q,))
q2 = Process(target=process_data, args=(q,))
q1.start()
q2.start()
if __name__ == '__main__':
main()
5.进程池
当需要创建的子进程数量不多时,我们可以直接利用multiporcessing中的Process动态生成多个进程,但是如果现在有100个任务需要处理,那我们需要多少个子进程呢,如果我们创建100个子进程也可以实现,但是资源比较浪费。我们也可以创建指定个数个子进程,例如只创建10个子进程,让着10个子进程重复的执行任务,这样就节约了资源。
就比如我们去景区湖上游玩,游船是重复利用的。
我们可以使用multiprocessing模块提供的Pool类,也就是进程池,可以达到进程重复利用。
创建进程池对象的时候可以指定一个最大进程数,当有新的请求提交到进程池中,如果池中的进程数还没有满,那么就会创建一个新的进程用来执行该请求,但是如果池中的进程数满了,该请求就会等待,直到进程池中的进程有结束的了,才会使用这个结束的进程来执行新的任务。
join 主进程等待所有子进程执行完毕,必须在close之后。
close 等待所有进程结束才关闭线程池
from multiprocessing import Process, Pool
import time
def foo(i):
print('i的值是---》', i)
time.sleep(2)
print('end...')
if __name__ == '__main__':
pool = Pool(3) # 创建进程池对象
for i in range(10):
pool.apply_async(func=foo, args=(i,)) # 将任务添加到进程池中
pool.close() # 关闭进程池
pool.join() # 主进程等待所有子进程执行完毕后主进程才往下执行,必须在close后面