并发编程(初学)
一、操作系统的发展史
1、第一代计算机:真空管和穿孔卡片
- 特点:没有操作系统的概念,所有的程序设计都是直接操控硬件
- 优点:程序员在申请的时间段内独享整个资源,可以即时地调试自己的程序
- 缺点:浪费计算机资源,一个时间段内只有一个人用
2、第二代计算机:晶体管和批处理系统
-
特点:有了操作系统的概念,有了程序设计语言:FORTRAN语言或汇编语言,写到纸上,然后穿孔打成卡片,再讲卡片盒带到输入室,交给操作员,然后喝着咖啡等待输出接口
-
优点:批处理,节省了机时和资源
-
缺点:1.整个流程需要人参与控制,将磁带搬来搬去
2.计算的过程仍然是顺序计算-》串行
3.程序员原来独享一段时间的计算机,现在必须被统一规划到一批作业中,等待结果和重新调试的过程都需要 等同批次的其他程序都运作完才可以(这极大的影响了程序的开发效率,无法及时调试程序)
3、第三代计算机:集成电路复用和多道程序设计
-
多道技术:
1、产生背景:针对单核,实现并发
(现在的主机一般是多核,那么每个核都会利用多道技术。有4个cpu,运行于cpu1的某个程序遇到io阻塞,会等到io结束再重新调度,会被调度到4个cpu中的任意一个,具体由操作系统调度算法决定。
2、空间复用:共用一个内存条,每一个进程都有自己独立的内存空间,互不干扰,物理级别的隔离
3、时间复用:共用一个cpu,IO的时候占用时间过长
4、第四代计算机:个人计算机
二、必备的理论基础
1、操作系统的作用:
1、隐藏丑陋复杂的硬件接口,提供良好的抽象接口
2、管理、调度进程,并且将多个进程对硬件的竞争变得有序
2、cpu切换:
遇到io切换,占用cpu时间过长也切换,核心在于切之前将进程的状态保存下来,这样才能保证下次切换回来时,能基于上次切走的位置继续运行
三、进程
- 进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。
- 进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行之),它才能成为一个活动的实体,我们称其为进程。
进程是操作系统中最基本、重要的概念。是多道程序系统出现后,为了刻画系统内部出现的动态情况,描述系统内部各道程序的活动规律引进的一个概念,所有多道程序设计操作系统都建立在进程的基础上。
四、创建并开启子进程的两种方式
4.1 方式一
#单个进程
from multiprocessing import Process
import time
def Text():
print('我是一个子进程')
print('我的进程开始了')
time.sleep(2)
print('我的进程结束了')
if __name__ == '__main__': #windows下必须要写这一句,不然会报错
p = Process(target = Text) #实例化产生一个对象
p.start() # 告诉操作系统我要开子进程,告诉完了这行代码就算执行完了,接着往下走,具体操作系统什么时候开子,开多长时间跟你没关系
time.sleep(5)
print('我是主进程,我要结束了')
#多个进程
#多个进程
from multiprocessing import Process
import time
def Text(x):
print(f'我是子进程{x}')
print(f'{x}的进程开始了')
time.sleep(2)
print(f'{x}的进程结束了')
if __name__ == '__main__': #windows下必须要写这一句,不然会报错
p1 = Process(target = Text,args=('yjy',)) #实例化产生一个对象 子进程1
p2 = Process(target = Text,args=('ypp',)) #实例化产生一个对象 子进程2
p1.start() # 告诉操作系统我要开子进程,告诉完了这行代码就算执行完了,接着往下走,具体操作系统什么时候开子,开多长时间跟你没关系
p2.start() # 告诉操作系统我要开子进程,告诉完了这行代码就算执行完了,接着往下走,具体操作系统什么时候开子,开多长时间跟你没关系
time.sleep(5)
print('我是主进程,我要结束了')
4.2 方式二
from multiprocessing import Process
import time
class Text(Process): #定义一个类,让他继承Process
def __init__(self,name): #重写__init__
super().__init__() #继承父类的__init__方法
self.name = name
def run(self): #定义一个函数
print(f'子进程的姓名为{self.name},子进程开始')
time.sleep(2)
print(f'{self.name}进程结束')
if __name__ == '__main__':
p = Text('yjy')
p.start() #通知操作系统子进程开始
print('主进程结束')
五、验证进程的内存空间隔离
from multiprocessing import Process
import time
x = 0 #定义一个变量x
def Check():
global x
x = 100
print(f'子进程的x:{x}')
if __name__ == '__main__':
p = Process(target=Check)
p.start()
print(f'父进程的x:{x}')
父进程的x:0
子进程的x:100
父进程的x和子进程的x不是一个值,说明他们两个进程互不影响!
六、僵尸进程和孤儿进程
僵尸进程(有害):当子进程结束的时候,不会将所有内容都释放掉,他会留下自己的pid(类似身份证号码)供父进程来查看,此所谓进程死了没死干净,这就是僵尸进程,他会等待父进程最后把他处理掉
孤儿进程(无害):一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。