day30_8.9 操作系统与并发编程
阅读原文时间:2023年07月09日阅读:1

一。操作系统相关

  1.手工操作

  1946年第一台计算机诞生--20世纪50年代中期,计算机工作还在采用手工操作方式。此时还没有操作系统的概念。

  这时候的计算机是由人为将穿孔的纸带装入输入机,控制台获取到数据和操作后进行计算,计算完后打印结果,最后用户取走纸带放入下一个用户的纸带。

  手工操作方式两个特点:

  (1)用户独占全机。不会出现因资源已被其他用户占用而等待的现象,但资源的利用率低。

  (2)CPU 等待手工操作。CPU的利用不充分。

  2.批处理  

手工搬运纸带输入操作,这一方式中人为干预的时间远远超过了计算机计算的时间,在搬运纸带的过程中,cpu处于高度空闲状态,为了解决这种人机矛盾,第一代类似于操作系统的批处理系统出现了。

  将成批的指令输入输入机,输入机由卫星机获取数据转交给高速磁带,高速磁带再将数据交给主机,输出也是通过卫星机。

  卫星机是一种不与主机直接相连的专门用于与输入输出设备打交道的。

  其功能是:

  (1)从输入机上读取用户作业并放到输入磁带上。

  (2)从输出磁带上读取执行结果并传给输出机。

  不足:每次主机内存中仅存放一道作业,每当它运行期间发出输入/输出(I/O)请求后,高速的CPU便处于等待低速的I/O完成状态,致使CPU空闲。

  3.多道程序设计。

  因为初始批处理系统只能同时操作一个程序,当程序进入i/o操作时,cpu会出现大量空闲时间。cpu利用率大大降低。为了解决这个问题,躲到程序系统产生了。

  多道程序系统就是在程序进入io操作时,切换另一个程序的cpu运算工作,然后循环切换程序进行cpu操作,这样的方式称为统筹,这样的cpu利用率才是最大的。

  4.分时系统。

  为了使得在电脑上可以同时运行两个程序,比如边用qq聊天边听音乐,操作系统将cpu对程序的计算分成很小的时间片,这些时间片轮流执行各个程序,看起来就像时同时计算一样,但是强行结束程序会大大减少运算效率,但是这样时为了实现看起来共同执行的效果。

  总结

  总之,为了充分利用cpu,操作系统会想办法让cpu不断地进行计算。、

二。进程

  进程就是一个正在执行的程序,是操作系统提供的最古老的抽象概念之一。

  1.多道技术。

  (1)空间上的复用

    多个程序共用一套计算机硬件。

  (2)时间上的复用

    实现时间复用就是用切换+保存状态实现。

    当一个程序遇到IO操作 操作系统会剥夺该程序的cpu执行权限(提高了cpu的利用率 并且也不影响程序的执行效率)

    当一个程序长时间占用cpu 操作系统也会剥夺该程序的cpu执行权限(降低了程序的执行效率)

  主流的进程调度算法:

  1:先来先服务调度算法。

  当程序需要cpu计算的时候,cpu会按照先来后到的顺序计算他们。每当一个进程计算完后,在计算下一个进程。

  这种算法,进程很不灵活,很容易出现后来的短作业一直没有运行,有利于长作业,不利于短作业。

  2.短作业优先调度算法。

  这种算法是判断后来的进程大小,将短作业优先执行,但是未来作业的长短是很难被计算出来,所以有根据作业剩余的长度来判断该分配先后顺序。

  是活动的算法。

  3.时间片轮转法

  将每个进程分成相同的时间片,按照时间片来执行。

  4多级反馈队列

  对后来的进程分配低级的优先级,每当各个进程等待了一定时间就将其优先级加一,每当其进程执行了一定时间,就将其优先级减一。执行优先级最高的进程

  创建进程的方式:

  创建进程需要使用multiprocess模块中的Process模块

import time
from multiprocessing import Process

def run():
print('进程开始')
time.sleep(3)
print('进程结束')

if __name__ == '__main__':
p=Process(target=run)
p.start()
print('主进程结束')

  在创建进程的时候使用process关键字,其中target参数传入的是你要运行的进程的函数,而当这个函数是有参函数时,需要用args对其参数进行赋值。

  注意:在这个py文件下创建进程,需要在main函数中创建,

  就进程创建的原理来看,当执行process时,会将该py文件当作模块导入,如果不写在main中,会出现循环导入情况。

  process创建进程时会新开辟一个内存空间。  

  当然,创建进程的方式也可以将函数放入一个类中,这个类继承自process。

  其中当创建子进程时,需要一小短时间,但即使这样主进程也不会等待这个是时间,会立即执行下面的代码,当创建空间完成才会执行,所以这是一个异步的情况。

class Myprocess(Process):
def __init__(self,name):
super().__init__()
self.name = name

def run(self):  
    print('%s开始运行'%self.name)  
    time.sleep(3)  
    print('%s运行结束'%self.name)

if __name__ == '__main__':
p = Myprocess('子进程')
p.start()
print('主进程结束')

  以上是使用类创建一个子进程的方法,将一个继承自process的类中,改写其中的run方法,当创建一个对象后,调用其中的start方法就会自动调用这个方法。

  join方法

  join方法其作用就是等待子进程运行结束才会继续执行主进程。在start方法之后使用。

class Myprocess(Process):
def __init__(self,name):
super().__init__()
self.name = name

def run(self):  
    print('%s开始运行'%self.name)  
    time.sleep(3)  
    print('%s运行结束'%self.name)

if __name__ == '__main__':
p = Myprocess('子进程')
p.start()
p.join()
print('主进程结束')
#子进程开始运行
#子进程运行结束
#主进程结束

  多个join重复调用时,最后长的那个子进程结束后才会继续执行主程序。

  进程的数据隔离

  在进程中,使用的是新开辟的内存空间,与主进程中的数据不会重叠。

from multiprocessing import Process

money = 100

def test():
global money
money = 100000

if __name__ == '__main__':
p = Process(target=test)
p.start()
print(money)
#

  即使在子进程中的函数,调用global改变全局变量,也只是改变子进程中的money,主进程没有影响。

  terminate()与is_alive()

  这两个方法都是有关进程的方法。

  terminate是将一个进程杀手,而is_alive是判断这个进程是否还活着

import time
from multiprocessing import Process

money = 100

def test():
global money
money = 100000

if __name__ == '__main__':
p = Process(target=test)
p.start()
p.terminate()
time.sleep(1)
a=p.is_alive()
print(a)
#False

  daemon

daemon是当该值被赋予True时,将主进程设置为守护进程,

  守护进程就是当主进程死亡后,无论子进程有没有运行结束,都会立即死亡。

from multiprocessing import Process
import time

def test(name):
print('%s总管正常活着'%name)
time.sleep(3)
print('%s总管正常死亡'%name)

if __name__ == '__main__':
p = Process(target=test,args=('egon',))
p.daemon = True # 将该进程设置为守护进程 这一句话必须放在start语句之前 否则报错
p.start()
time.sleep(0.1)
print('皇帝jason寿正终寝')
#皇帝jason寿正终寝

  如果没有这个守护进程的设定,就会等主进程正常结束后,子进程再正常结束。

三。术语解释

  1.同步异步  

  同步异步是表示的任务的提交方式。

  同步:当任务提交之后,会原地等待任务的指向并拿到返回结果才走,期间不做任何事情,就好像程序卡住了一样。

  异步:当任务提交了之后,就不再原地等待,而是继续执行下一行代码(结果会通过其他方式获取)

  2.阻塞非阻塞

  阻塞:阻塞态

  非阻塞:就是就绪态,运行态

  3.僵尸进程与孤儿进程

  僵尸进程:一个进程结束后,会留下占用的端口号(所有进程都会步入僵尸进程)

  孤儿进程:当一个父进程下有一个子进程,父进程意外结束后,并没有把子进程正常结束,子进程就会留下占用的端口号。

  针对linux会有一个init,将意外结束的父进程下的子进程回收。

四。互斥锁

  当多个进程操作同一份数据的时候 会造成数据的错乱,这个时候必须加锁处理,将并发变成串行,虽然降低了效率但是提高了数据的安全

  注意:

  1.锁不要轻易使用 容易造成死锁现象

  2.只在处理数据的部分加锁 不要在全局加锁

  锁必须在主进程中产生 交给子进程去使用。

  已抢票为例

from multiprocessing import Process,Lock
import time
import json

查票

def search(i):
with open('data','r',encoding='utf-8') as f:
data = f.read()
t_d = json.loads(data)
print('用户%s查询余票为:%s'%(i,t_d.get('ticket')))

买票

def buy(i):
with open('data','r',encoding='utf-8') as f:
data = f.read()
t_d = json.loads(data)
time.sleep(1)
if t_d.get('ticket') > 0:
# 票数减一
t_d['ticket'] -= 1
# 更新票数
with open('data','w',encoding='utf-8') as f:
json.dump(t_d,f)
print('用户%s抢票成功'%i)
else:
print('没票了')

def run(i,mudex):
search(i)
mudex.acquire()
buy(i)
mudex.release()

if __name__ == '__main__':
mudex = Lock()
for i in range(10):
p = Process(target=run,args=(i,mudex))
p.start()
#用户0查询余票为:5
#用户1查询余票为:5
#用户2查询余票为:5
#用户3查询余票为:5
#用户4查询余票为:5
#用户0抢票成功
#用户5查询余票为:4
#用户6查询余票为:4
#用户7查询余票为:4
#用户8查询余票为:4
#用户9查询余票为:4
#用户1抢票成功
#用户2抢票成功
#用户3抢票成功
#用户4抢票成功
#没票了
#没票了
#没票了
#没票了
#没票了

  如果没有锁,当显示完票数后,大家的权限都是可以抢票的,而进程的运行会比较快,同时几个进程都去执行买票的方法,就会出现一张票被买了很多次。

  这里有一个疑问,为什么后来的操作买票成功后,数据不会执行减法操作将数据减成负的