目录
7操作系统发展史
1操作系统
- 为什么要有操作系统
- 管理控制协调计算机中硬件与软件的关系
操作系统的作用?
精简的说的话,操作系统就是一个协调、管理和控制计算机硬件资源和软件资源的控制程序。操作系统所处的位置如图1
#操作系统位于计算机硬件与应用软件之间,本质也是一个软件。操作系统由操作系统的内核(运行于内核态,管理硬件资源)以及系统调用(运行于用户态,为应用程序员写的应用程序提供系统调用接口)两部分组成,所以,单纯的说操作系统是运行于内核态的,是不准确的。
细说的话,操作系统应该分成两部分功能:
#一:隐藏了丑陋的硬件调用接口(键盘、鼠标、音箱等等怎么实现的,就不需要你管了),为应用程序员提供调用硬件资源的更好,更简单,更清晰的模型(系统调用接口)。应用程序员有了这些接口后,就不用再考虑操作硬件的细节,专心开发自己的应用程序即可。 例如:操作系统提供了文件这个抽象概念,对文件的操作就是对磁盘的操作,有了文件我们无需再去考虑关于磁盘的读写控制(比如控制磁盘转动,移动磁头读写数据等细节), #二:将应用程序对硬件资源的竞态请求变得有序化 例如:很多应用软件其实是共享一套计算机硬件,比方说有可能有三个应用程序同时需要申请打印机来输出内容,那么a程序竞争到了打印机资源就打印,然后可能是b竞争到打印机资源,也可能是c,这就导致了无序,打印机可能打印一段a的内容然后又去打印c...,操作系统的一个功能就是将这种无序变得有序。
#作用一:为应用程序提供如何使用硬件资源的抽象 例如:操作系统提供了文件这个抽象概念,对文件的操作就是对磁盘的操作,有了文件我们无需再去考虑关于磁盘的读写控制 注意: 操作系统提供给应用程序的该抽象是简单,清晰,优雅的。为何要提供该抽象呢? 硬件厂商需要为操作系统提供自己硬件的驱动程序(设备驱动,这也是为何我们要使用声卡,就必须安装声卡驱动。。。),厂商为了节省成本或者兼容旧的硬件,它们的驱动程序是复杂且丑陋的 操作系统就是为了隐藏这些丑陋的信息,从而为用户提供更好的接口 这样用户使用的shell,Gnome,KDE看到的是不同的界面,但其实都使用了同一套由linux系统提供的抽象接口 #作用二:管理硬件资源 现代的操作系统运行同时运行多道程序,操作系统的任务是在相互竞争的程序之间有序地控制对处理器、存储器以及其他I/O接口设备的分配。 例如: 同一台计算机上同时运行三个程序,它们三个想在同一时刻在同一台计算机上输出结果,那么开始的几行可能是程序1的输出,接着几行是程序2的输出,然后又是程序3的输出,最终将是一团糟(程序之间是一种互相竞争资源的过程) 操作系统将打印机的结果送到磁盘的缓冲区,在一个程序完全结束后,才将暂存在磁盘上的文件送到打印机输出,同时其他的程序可以继续产生更多的输出结果(这些程序的输出没有真正的送到打印机),这样,操作系统就将由竞争产生的无序变得有序化。
2操作系统的发展史
第一代(1940~1955) 手工操作----穿孔卡片
第一代计算机的产生背景:
第一代之前人类是想用机械取代人力,第一代计算机的产生是计算机由机械时代进入电子时代的标志,从Babbage失败之后一直到第二次世界大战,数字计算机的建造几乎没有什么进展,第二次世界大战刺激了有关计算机研究的爆炸性进展。
lowa州立大学的john Atanasoff教授和他的学生Clifford Berry建造了据认为是第一台可工作的数字计算机。该机器使用300个真空管。大约在同时,Konrad Zuse在柏林用继电器构建了Z3计算机,英格兰布莱切利园的一个小组在1944年构建了Colossus,Howard Aiken在哈佛大学建造了Mark 1,宾夕法尼亚大学的William Mauchley和他的学生J.Presper Eckert建造了ENIAC。这些机器有的是二进制的,有的使用真空管,有的是可编程的,但都非常原始,设置需要花费数秒钟时间才能完成最简单的运算。
在这个时期,同一个小组里的工程师们,设计、建造、编程、操作及维护同一台机器,所有的程序设计是用纯粹的机器语言编写的,甚至更糟糕,需要通过成千上万根电缆接到插件板上连成电路来控制机器的基本功能。没有程序设计语言(汇编也没有),操作系统则是从来都没听说过。使用机器的过程更加原始,详见下‘工作过程’
特点:
没有操作系统的概念
所有的程序设计都是直接操控硬件
工作过程:
程序员在墙上的机时表预约一段时间,然后程序员拿着他的插件版到机房里,将自己的插件板街道计算机里,这几个小时内他独享整个计算机资源,后面的一批人都得等着(两万多个真空管经常会有被烧坏的情况出现)。
后来出现了穿孔卡片,可以将程序写在卡片上,然后读入机而不用插件板
优点:
程序员在申请的时间段内独享整个资源,可以即时地调试自己的程序(有bug可以立刻处理)
缺点:
浪费计算机资源,一个时间段内只有一个人用。
注意:同一时刻只有一个程序在内存中,被cpu调用执行,比方说10个程序的执行,是串行的
穿孔卡带的过程:程序员将对应于程序和数据的已穿孔的纸带(或卡片)装入输入机,然后启动输入机把程序和数据输入计算机内存,接着通过控制台开关启动程序针对数据运行;计算完毕,打印机输出计算结果;用户取走结果并卸下纸带(或卡片)后,才让下一个用户上机。
第二代(1955~1965) 磁带存储---批处理系统
第二代计算机的产生背景:
由于当时的计算机非常昂贵,自认很自然的想办法较少机时的浪费。通常采用的方法就是批处理系统。
特点:
设计人员、生产人员、操作人员、程序人员和维护人员直接有了明确的分工,计算机被锁在专用空调房间中,由专业操作人员运行,这便是‘大型机’。
有了操作系统的概念
有了程序设计语言:FORTRAN语言或汇编语言,写到纸上,然后穿孔打成卡片,再讲卡片盒带到输入室,交给操作员,然后喝着咖啡等待输出接口
工作过程:插图
第二代如何解决第一代的问题/缺点:
1.把一堆人的输入攒成一大波输入,
2.然后顺序计算(这是有问题的,但是第二代计算也没有解决)
3.把一堆人的输出攒成一大波输出
现代操作系统的前身:(见图)
优点:批处理,节省了机时
缺点:1.整个流程需要人参与控制,将磁带搬来搬去(中间俩小人)
2.计算的过程仍然是顺序计算-》串行
3.程序员原来独享一段时间的计算机,现在必须被统一规划到一批作业中,等待结果和重新调试的过程都需要等同批次的其他程序都运作完才可以(这极大的影响了程序的开发效率,无法及时调试程序)
第三代(1955~1965)集成电路,多道程序系统
第三代计算机的产生背景:
20世纪60年代初期,大多数计算机厂商都有两条完全不兼容的生产线。
一条是面向字的:大型的科学计算机,如IBM 7094,见上图,主要用于科学计算和工程计算
另外一条是面向字符的:商用计算机,如IBM 1401,见上图,主要用于银行和保险公司从事磁带归档和打印服务
开发和维护完全不同的产品是昂贵的,同时不同的用户对计算机的用途不同。
IBM公司试图通过引入system/360系列来同时满足科学计算和商业计算,360系列低档机与1401相当,高档机比7094功能强很多,不同的性能卖不同的价格
360是第一个采用了(小规模)芯片(集成电路)的主流机型,与采用晶体管的第二代计算机相比,性价比有了很大的提高。这些计算机的后代仍在大型的计算机中心里使用,此乃现在服务器的前身,这些服务器每秒处理不小于千次的请求。
如何解决第二代计算机的问题1:
卡片被拿到机房后能够很快的将作业从卡片读入磁盘,于是任何时刻当一个作业结束时,操作系统就能将一个作业从磁带读出,装进空出来的内存区域运行,这种技术叫做
同时的外部设备联机操作:SPOOLING,该技术同时用于输出。当采用了这种技术后,就不在需要IBM1401机了,也不必将磁带搬来搬去了(中间俩小人不再需要)
如何解决第二代计算机的问题2:
第三代计算机的操作系统广泛应用了第二代计算机的操作系统没有的关键技术:多道技术
cpu在执行一个任务的过程中,若需要操作硬盘,则发送操作硬盘的指令,指令一旦发出,硬盘上的机械手臂滑动读取数据到内存中,这一段时间,cpu需要等待,时间可能很短,但对于cpu来说已经很长很长,长到可以让cpu做很多其他的任务,如果我们让cpu在这段时间内切换到去做其他的任务,这样cpu不就充分利用了吗。这正是多道技术产生的技术背景
多道技术:
多道技术中的多道指的是多个程序,多道技术的实现是为了解决多个程序竞争或者说共享同一个资源(比如cpu)的有序调度问题,解决方式即多路复用,多路复用分为时间上的复用和空间上的复用。
空间上的复用:将内存分为几部分,每个部分放入一个程序,这样,同一时间内存中就有了多道程序。
时间上的复用:当一个程序在等待I/O时,另一个程序可以使用cpu,如果内存中可以同时存放足够多的作业,则cpu的利用率可以接近100%,类似于我们小学数学所学的统筹方法。(操作系统采用了多道技术后,可以控制进程的切换,或者说进程之间去争抢cpu的执行权限。这种切换不仅会在一个进程遇到io时进行,一个进程占用cpu时间过长也会切换,或者说被操作系统夺走cpu的执行权限)
空间上的复用最大的问题是:程序之间的内存必须分割,这种分割需要在硬件层面实现,由操作系统控制。如果内存彼此不分割,则一个程序可以访问另外一个程序的内存,
首先丧失的是安全性,比如你的qq程序可以访问操作系统的内存,这意味着你的qq可以拿到操作系统的所有权限。
其次丧失的是稳定性,某个程序崩溃时有可能把别的程序的内存也给回收了,比方说把操作系统的内存给回收了,则操作系统崩溃。
第三代计算机的操作系统仍然是批处理
许多程序员怀念第一代独享的计算机,可以即时调试自己的程序。为了满足程序员们很快可以得到响应,出现了分时操作系统
如何解决第二代计算机的问题3:
分时操作系统:
多个联机终端+多道技术
20个客户端同时加载到内存,有17在思考,3个在运行,cpu就采用多道的方式处理内存中的这3个程序,由于客户提交的一般都是简短的指令而且很少有耗时长的,索引计算机能够为许多用户提供快速的交互式服务,所有的用户都以为自己独享了计算机资源
CTTS:麻省理工(MIT)在一台改装过的7094机上开发成功的,CTSS兼容分时系统,第三代计算机广泛采用了必须的保护硬件(程序之间的内存彼此隔离)之后,分时系统才开始流行
MIT,贝尔实验室和通用电气在CTTS成功研制后决定开发能够同时支持上百终端的MULTICS(其设计者着眼于建造满足波士顿地区所有用户计算需求的一台机器),很明显真是要上天啊,最后摔死了。
后来一位参加过MULTICS研制的贝尔实验室计算机科学家Ken Thompson开发了一个简易的,单用户版本的MULTICS,这就是后来的UNIX系统。基于它衍生了很多其他的Unix版本,为了使程序能在任何版本的unix上运行,IEEE提出了一个unix标准,即posix(可移植的操作系统接口Portable Operating System Interface)
后来,在1987年,出现了一个UNIX的小型克隆,即minix,用于教学使用。芬兰学生Linus Torvalds基于它编写了Linux
第四代(1980~至今)现代计算机
进入20世纪80年代,大规模集成电路工艺技术的飞跃发展,微处理机的出现和发展,掀起了计算机大发展大普及的浪潮。一方面迎来了个人计算机的时代,同时又向计算机网络、分布式处理、巨型计算机和智能化方向发展。于是,操作系统有了进一步的发展,如:个人计算机操作系统、网络操作系统、分布式操作系统等。
1.个人计算机操作系统
个人计算机上的操作系统是联机交互的单用户操作系统,它提供的联机交互功能与通用分时系统提供的功能很相似。
由于是个人专用,因此一些功能会简单得多。然而,由于个人计算机的应用普及,对于提供更方便友好的用户接口和丰富功能的文件系统的要求会愈来愈迫切。
2.网络操作系统
计算机网络:通过通信设施,将地理上分散的、具有自治功能的多个计算机系统互连起来,实现信息交换、资源共享、互操作和协作处理的系统。
网络操作系统:在原来各自计算机操作系统上,按照网络体系结构的各个协议标准增加网络管理模块,其中包括:通信、资源共享、系统安全和各种网络应用服务。
就是添加了一些网络方面的功能。
3.分布式操作系统
表面上看,分布式系统与计算机网络系统没有多大区别。分布式操作系统也是通过通信网络,将地理上分散的具有自治功能的数据处理系统或计算机系统互连起来,实现信息交换和资源共享,协作完成任务。——硬件连接相同。
分布式:将一个大的任务拆分成几个小的任务,分配给不同的任务处理机制,具体怎么分配是由系统中的算法决定的,大家同时来运行自己的任务,然后各自将人物的返回结果再返回给你这个大的任务。
但有如下一些明显的区别:
(1)分布式系统要求一个统一的操作系统,实现系统操作的统一性。
(2)分布式操作系统管理分布式系统中的所有资源,它负责全系统的资源分配和调度、任务划分、信息传输和控制协调工作,并为用户提供一个统一的界面。
(3)用户通过这一界面,实现所需要的操作和使用系统资源,至于操作定在哪一台计算机上执行,或使用哪台计算机的资源,则是操作系统完成的,用户不必知道,此谓:系统的透明性。
(4)分布式系统更强调分布式计算和处理,因此对于多机合作和系统重构、坚强性和容错能力有更高的要求,希望系统有:更短的响应时间、高吞吐量和高可靠性。
分布式系统已经很普遍了,一般个人用不到,企业在处理比较大的任务的时候才会使用。
3进程的理论
串行: 所有的任务一个一个的完成.
并发: 一个cpu完成多个任务.看起来像是同时完成.
并行: 多个cpu执行多个任务,真正的同时完成.
阻塞: cpu遇到IO就是阻塞.
非阻塞: 没有IO,就叫非阻塞.
程序: 一堆静态文件
一个正在执行的程序任务,一个进程.
一个程序能否开启多个进程? 可以.
进程的创建:
一个子进程必须依赖于一个主进程才可以开启.一个主进程可以开启多个子进程.
unix: fork创建子进程.
unix(linux,mac): 创建一个子进程会完完全全复制一
个主进程所有的资源,初始资源不变.
windows: 操作系统调用CreateProcess 处理进程
的创建.
windows:创建一个子进程,会copy主进程所有的资
源,但是会改变一些资源.进程的状态:
8开启进程的方式
1.开启自进程的第一种方式:
from multiprocessing import Process import time def task(name): print(f"{name} is running") time.sleep(2) print(f"{name} is gone") if __name__ == "__main__":#在windows环境下, 开启进程必须在 __name__ == '__main__' 下面 p = Process(target=task,args=("q",)) p.start() # 只是向操作系统发出一个开辟子进程的信号,然后就执行下一行了. print("主") # 这个信号操作系统接收到之后,会从内存中开辟一个子进程空间,然后在将主进程所有数据copy加载到子进程,然后在调用cpu去执行. # 开辟子进程开销是很大的.
2.开启子进程的第二种方式
from multiprocessing import Process import time class MyProcess(Process): def __init__(self,name): super().__init__() self.name = name def run1(self): print(f"{self.name} is running") time.sleep(2) print(f"{self.name} is gone") if __name___ == "__main__": p = MyProcess("q") p.start() print("主")
9获取进程pid
os.getped() # 获取进程id os.fetppid() # 获取父进程if
import os import time from multiprocessing import Process def task(name): print(f"子进程:{os.getpid()}") print(f"父进程:{os.getppid()}") if __name__ == "__main__": p = Process(target=task,args=("q",)) p.start() print(f"==主{os.getpid()}")
10进程之间的空间隔离
from multiprocessing import Process import os import time name = "原来" def task(): global name name = "之后" print(f"子进程{name}") if __name__ == "__main__": p = Process(target=task) p.start() time.sleep(3) print(f"主:{name}") lst = [1] def task(): lst.append(2) print(f"子进程{lst}") if __name__ == "__main__": p = Process(target=task) p.start() time.sleep(3) print(f"主:{lst}")
11join
- join让主进程等待子进程结束之后,在执行主进程.
from multiprocessing import Process import time def task(name): print(f"{name} is running") time.sleep(2) print(f"{name] is gone") if __name__ == "__main__": p = Process(target=task,args=("1",)) p.start() p.join() print("主")
from multiprocessing import Process import time def task(sec): print("running") time.sleep(sec) print("gone") if __name__ == '__main__': start = time.time() lst = [] for i in range(1,4): p = Process(target=task,args=(i,)) lst.append(p) for i in lst: i.start() for i in lst: i.join() print(time.time() - start)
12进程的其他参数
from multiprocessing import Process import time def task(name): print(f"{name} is running") time.sleep(2) print(f"{name} is gone") if __name__ == "__main__": p = Process(target=task,args=('常鑫',),name='alex') # 创建一个进程对象 p.start() time.sleep(1) p.terminate() #杀死子进程 *** p.join() time.sleep(0.5) print(p.is_alive()) print(p.name) p.name = "sb" print("主")
13守护进程
- 子进程守护着主进程,只要主进程结束,子进程跟着就结束,
from multiprocessing inport Process import time def task(name): print(f"{name} is running") time.sleep(2) print(f"{name} is gone") if __name__ == "__main__": p = Process(target=task,args=("1",)) p.daemon() = True p.start() # p.daemon() = True time.sleep(1) print("主")
14僵尸进程 孤儿进程
僵尸进程孤儿进程
基于unix环境(linux,macOS)主进程需要等待子进程结束之后,主进程才结束
主进程时刻监测子进程的运行状态,当子进程结束之后,一段时间之内,将子进程进行回收.为什么主进程不在子进程结束后马上对其回收呢?
- 主进程与子进程是异步关系.主进程无法马上捕获子进程什么时候结束.
- 如果子进程结束之后马上再内存中释放资源,主进程就没有办法监测子进程的状态了.
unix针对于上面的问题,提供了一个机制.
所有的子进程结束之后,立马会释放掉文件的操作链接,内存的大部分数据,但是会保留一些内容: 进程号,结束时间,运行状态,等待主进程监测,回收.僵尸进程: 所有的子进程结束之后,在被主进程回收
之前,都会进入僵尸进程状态.僵尸进程有无危害???
如果父进程不对僵尸进程进行回收(wait/waitpid),产生大量的僵尸进程,这样就会占用内存,占用进程pid号.孤儿进程:
父进程由于某种原因结束了,但是你的子进程还在运行行中,这样你的这些子进程就成了孤儿进程.你的父进程如果结束了,你的所有的孤儿进程就会被init进程的回收,init就变成了你的父进程,对你进行回收.
僵尸进程如何解决???
父进程产生了大量子进程,但是不回收,这样就会形成大量的僵尸进程,解决方式就是直接杀死父进程,将所有的僵尸进程变成孤儿进程进程,由init进行回收.
from multiprocessing import Process import time import os def task(name): print(f'{name} is running') print(f'主进程: {os.getppid()}') print(f'子进程: {os.getpid()}') time.sleep(50) print(f'{name} is gone') if __name__ == '__main__': # 在windows环境下, 开启进程必须在 __name__ == '__main__' 下面 p = Process(target=task,args=('常鑫',)) # 创建一个进程对象 p.start() print('==主开始')
15互斥锁
- acquire release
- 不能锁俩次
from multiprocessing import Process from multiprocessing import Lock import time import random import os def task1(p,lock): ''' 一把锁不能连续锁两次 lock.acquire() lock.acquire() lock.release() lock.release() ''' lock.acquire() print(f'{p}开始打印了') time.sleep(random.randint(1,3)) print(f'{p}打印结束了') lock.release() def task2(p,lock): lock.acquire() print(f'{p}开始打印了') time.sleep(random.randint(1,3)) print(f'{p}打印结束了') lock.release() def task3(p,lock): lock.acquire() print(f'{p}开始打印了') time.sleep(random.randint(1,3)) print(f'{p}打印结束了') lock.release() if __name__ == '__main__': mutex = Lock() p1 = Process(target=task1,args=('p1',mutex)) p2 = Process(target=task2,args=('p2',mutex)) p3 = Process(target=task3,args=('p3',mutex)) p2.start() p1.start() p3.start()
- 当很多进程共强一个资源(数据)时, 你要保证顺序(数据的安全),一定要串行.
- 互斥锁: 可以公平性的保证顺序以及数据的安全.
- 基于文件的进程之间的通信:
- 效率低.
- 自己加锁麻烦而且很容易出现死锁.
16进程之间的通信
16.1基于文件进程之间的通信
- 当很多进程共强一个资源(数据)时, 你要保证顺序(数据的安全),一定要串行.
- 互斥锁: 可以公平性的保证顺序以及数据的安全.
from multiprocessing import Process,Lock import time,os,random,json def search(): time.sleep(random.randint(1,3)) with open("ticket.json",mode="r",encoding="utf-8")as f: dic = json.load(f) print(f"{os.getpid()}查看了票数,剩余{dic['count']}张") def paid(): with open("ticket.json", mode="r", encoding="utf-8")as f: dic = json.load(f) if dic["count"] > 0: dic["count"] -= 1 time.sleep(random.randint(1,3)) with open("ticket.json", mode="w", encoding="utf-8")as f: json.dump(dic,f) print(f"{os.getpid()}购买成功!") def task(lock): search() lock.acquire() paid() lock.release() if __name__ == '__main__': mutex = Lock() for i in range(6): p = Process(target=task,args=(mutex,)) p.start()
16.2基于队列进程之间的通信
- 队列: 把队列理解成一个容器,这个容器可以承载一些数据,
- 队列的特性: 先进先出永远保持这个数据. FIFO 羽毛球筒.
from multiprocessing import Queue q = Queue() def func(): print("in func") q.put(1) q.put("zhy") q.put([1,2,3]) q.put(func) print(q.get()) print(q.get()) print(q.get()) f = q.get() f()
- 当队列满了时,在进程put数据就会阻塞.
- 当数据取完时,在进程get数据也会出现阻塞,直到某一个进程put数据.
=from multiprocessing import Queue =q = Queue(3) q.put(1) q.put('alex') q.put([1,2,3]) q.put(5555) # 当队列满了时,在进程put数据就会阻塞. q.get() print(q.get()) print(q.get()) print(q.get()) print(q.get()) # 当数据取完时,在进程get数据也会出现阻塞,直到某一个进程put数据.
16.2.1 get put 重要参数
- block=False 只要遇到阻塞就会报错. 默认为True
- timeout=3 阻塞3秒,3秒之后就会报错
from multiprocessing import Queue q = Queue(3) # maxsize q.put(1) q.put('alex') q.put([1,2,3]) print(q.get()) print(q.get()) print(q.get()) print(q.get(timeout=3)) # 阻塞3秒,3秒之后还阻塞直接报错.
17生产拿消费者模型
进程: 生产者消费者模型
编程思想,模型,设计模式,理论等等,都是交给你一种编程
的方法,以后你遇到类似的情况,套用即可.
生产者消费者模型三要素:
生产者: 产生数据的
消费者: 接收数据做进一步处理的
from multiprocessing import Process from multiprocessing import Queue import time import random def producer(q,name): for i in range(1,6): time.sleep(random.ranint(1,2)) res = f"{i}号包子" q.put(res) print(f"生产者{name} 生产了{res}") def consumer(q,name): while 1: try: food = q.get(timeout=3) time.sleep(random.randint(1,3)) print(f"消费者{name}吃了{food}") except Exception: return if __name__ == "__main__": q = Queue() p1 = Process(target=producer,args=(q,"sb")) p2 = Process(target=consumer,args=(q,"二狗")) p1.start() p2.start()
18开启线程的两种方式
什么是线程?
一条流水线的工作流程.
进程: 在内存中开启一个进程空间,然后将主进程的
所有的资源数据复制一份,然后调用cpu去执行这些
代码.
之前的描述不够具体:
开启一个进程:
在内存中开启一个进程空间,然后将主进程的所有的
资源数据复制一份,然后调用线程去执行代码
进程是资源单位, 线程是执行单位.
以后你描述开启一个进程:
开启一个进程:进程会在内存中开辟一个进程空间,
将主进程的资料数据全部复制一份,线程会执行里面
的代码.
from threading import Thread import time def task(name): print(f"{name} is running") time.sleep(1) print(f"{name} is gone") if __name__ == "__main__": t1 = Thread(target=task,args=("二狗",)) t1.start() print("主线程")
from threading import Thread import time class MyThread(Thread): def __init__(self,name,l1,s1): super().__init__() self.name = name self.l1 = l1 self.s1 = s1 def run(self): print(f"{self.name} is running") time.sleep(1) print(f"{self.name} is gone") if __name__ == "__main__": t1 = MYTHread("李业",[1,2,3],"a") t1.start() print("主线程")
19多线程与多进程开启速度的区别
- 开启进程的开销非常大,比开启线程的开销大很
多. - 开启线程的速度非常快.要快几十倍到上百倍.
- 线程线程之间可以共享数据,进程与进程之间需借
助队列等方法实现通信.
# 多进程: from threading import Thread from multiprocessing import Process import os def work(): print('hello') if __name__ == '__main__': #在主进程下开启线程 t=Process(target=work) t.start() print('主线程/主进程')
# 多线程 from threading import Thread import time def task(name): print(f'{name} is running') time.sleep(1) print(f'{name} is gone') if __name__ == '__main__': t1 = Thread(target=task,args=('海狗',)) t1.start() print('===主线程') # 线程是没有主次之分的.
20线程进程pid 线程内数据
一个进程中的所有线程pid是一样的
同一进程内的资源数据对于这个进程的多个线程来说是共享的.
from threading import Thread import os x = 3 def task(): global x x = 100 if __name__ == '__main__': t1 = Thread(target=task) t1.start() t1.join() print(f'===主线程{x}')
21线程的其他方法
- isAlive() 判断线程是否活着
- getName() 获取线程名
- t1.name() 获取线程名 ***
- currentThread() 获取当前进程的对象
- enumerate() 返回一个列表包含所有的线程对象
- activeCount() 线程数****
from threading import Thread from threading import currentThread from threading import enumerate from threading import activeCount import os import time # x = 3 def task(): # print(currentThread()) time.sleep(1) print('666') print(123) if __name__ == '__main__': # t1 = Thread(target=task,name='线程1') t2 = Thread(target=task,name='线程2') # name 设置线程名 t1.start() t2.start() # time.sleep(2) # print(t1.isAlive()) # 判断线程是否活着 # print(t1.getName()) # 获取线程名 # t1.setName('子线程-1') # print(t1.name) # 获取线程名 *** # # threading方法 # print(currentThread()) # 获取当前线程的对象 # print(enumerate()) # 返回一个列表,包含所有的线程对象 print(activeCount()) # *** print(f'===主线程{os.getpid()}')
22join与守护线程
- join: 阻塞 告知主线程要等待我子线程执行完毕之后再执行主线程
from threading import Thread import time def task(name): print(f'{name} is running') time.sleep(1) print(f'{name} is gone') if __name__ == '__main__': start_time = time.time() t1 = Thread(target=task,args=('海狗',)) t2 = Thread(target=task,args=('海狗1',)) t3 = Thread(target=task,args=('海狗2',)) t1.start() t1.join() t2.start() t2.join() t3.start() t3.join() print(f'===主线程{time.time() - start_time}') # 线程是没有主次之分
- 主线程什么时候结束???
- 守护线程 等待非守护子线程以及主线程结束之后,结束.
from threading import Thread import time def foo(): print(123) time.sleep(3) print("end123") def bar(): print(456) time.sleep(1) print("end456") t1=Thread(target=foo) t2=Thread(target=bar) t1.daemon=True t1.start() t2.start() print("main-------")
22互斥锁
from threading import Thread from threading import Lock import time import random x = 100 def task(lock): lock.acquire() # time.sleep(random.randint(1,2)) global x temp = x time.sleep(0.01) temp = temp - 1 x = temp lock.release() if __name__ == '__main__': mutex = Lock() l1 = [] for i in range(100): t = Thread(target=task,args=(mutex,)) l1.append(t) t.start() time.sleep(3) print(f'主线程{x}')
23死锁现象递归锁 信号量
1.死锁
from multiprocessing import Thread,Lock import time import os lock_A = Lock() lock_b = Lock() class MyThread(Thread): def run(self): self.f1() self.f2() def f1(): lock_A.acquire() print(f"{os.getpid()}拿到了A锁") lock_B.acquire() print(f"{os.getpid()}拿到了B锁") lock_B.release() lock_A.release() def f2(): lock_B.acquire() print(f"{os.getpid()}拿到了B锁") time.sleep(1) lock_A.acquire() print(f"{os.getpid()}拿到了A锁") lock_A.release() lock_B.release() for i in range(3): t = MyThread() t.start()
2递归锁
递归锁可以解决死锁现象,业务需要多个锁时,先要考虑
递归锁.
from multiprocessing import Thread,RLock import time import os lock_A = lock_B = RLock() class MyThread(Thread): def run(self): self.f1() self.f2() def f1(): lock_A.acquire() print(f"{os.getpid()}拿到了A锁") lock_B.acquire() print(f"{os.getpid()}拿到了B锁") lock_B.release() lock_A.release() def f2(): lock_B.acquire() print(f"{os.getpid()}拿到了B锁") time.sleep(1) lock_A.acquire() print(f"{os.getpid()}拿到了A锁") lock_A.release() lock_B.release() for i in range(3): t = MyThread() t.start()
3.信号量
也是一种锁, 控制并发数量
from threading import Thread,Semaphore,current_thread import time import random sem = Semaphore(5) def task(): sem.acquire() print(f'{current_thread().name} 厕所 ing') time.sleep(random.randint(1,3)) sem.release() if __name__ == '__main__': for i in range(20): t = Thread(target=task,) t.start()
24GIL全局解释器锁 IO计算密集型验证
1.GIL全局解释器锁
- 理论上来说:单个进程的多线程可以利用多核.
- 但是,开发Cpython解释器的程序员,给进入解释器的线
程加了锁.
首先,一些语言(java、c++、c)是支持同一个进程中的多个线程是可以应用多核CPU的,也就是我们会听到的现在4核8核这种多核CPU技术的牛逼之处。那么我们之前说过应用多进程的时候如果有共享数据是不是会出现数据不安全的问题啊,就是多个进程同时一个文件中去抢这个数据,大家都把这个数据改了,但是还没来得及去更新到原来的文件中,就被其他进程也计算了,导致数据不安全的问题啊,所以我们是不是通过加锁可以解决啊,多线程大家想一下是不是一样的,并发执行就是有这个问题。但是python最早期的时候对于多线程也加锁,但是python比较极端的(在当时电脑cpu确实只有1核)加了一个GIL全局解释锁,是解释器级别的,锁的是整个线程,而不是线程里面的某些数据操作,每次只能有一个线程使用cpu,也就说多线程用不了多核,但是他不是python语言的问题,是CPython解释器的特性,如果用Jpython解释器是没有这个问题的,Cpython是默认的,因为速度快,Jpython是java开发的,在Cpython里面就是没办法用多核,这是python的弊病,历史问题,虽然众多python团队的大神在致力于改变这个情况,但是暂没有解决。(这和解释型语言(python,php)和编译型语言有关系吗???待定!,编译型语言一般在编译的过程中就帮你分配好了,解释型要边解释边执行,所以为了防止出现数据不安全的情况加上了这个锁,这是所有解释型语言的弊端??)
但是有了这个锁我们就不能并发了吗?当我们的程序是偏计算的,也就是cpu占用率很高的程序(cpu一直在计算),就不行了,但是如果你的程序是I/O型的(一般你的程序都是这个)(input、访问网址网络延迟、打开/关闭文件读写),在什么情况下用的到高并发呢(金融计算会用到,人工智能(阿尔法狗),但是一般的业务场景用不到,爬网页,多用户网站、聊天软件、处理文件),I/O型的操作很少占用CPU,那么多线程还是可以并发的,因为cpu只是快速的调度线程,而线程里面并没有什么计算,就像一堆的网络请求,我cpu非常快速的一个一个的将你的多线程调度出去,你的线程就去执行I/O操作了,****
2.IO计算密集型验证
io密集型:
计算密集型:
# 计算密集型 from multiprocessing import Process from threading import Thread import os,time def work(): res=0 for i in range(100000000): res*=i if __name__ == '__main__': l=[] print(os.cpu_count()) #本机为4核 start=time.time() for i in range(4): p=Process(target=work) #耗时5s多 p=Thread(target=work) #耗时18s多 l.append(p) p.start() for p in l: p.join() stop=time.time() print('run time is %s' %(stop-start))
# IO密集型 from multiprocessing import Process from threading import Thread import threading import os,time def work(): time.sleep(2) print('===>') if __name__ == '__main__': l=[] print(os.cpu_count()) #本机为4核 start=time.time() for i in range(400): # p=Process(target=work) #耗时12s多,大部分时间耗费在创建进程上 p=Thread(target=work) #耗时2s多 l.append(p) p.start() for p in l: p.join() stop=time.time() print('run time is %s' %(stop-start))
25多线程实现socket通信
# 服务端 import socket from threading import Thread def communicate(conn,addr): while 1: try: from_client_data = conn.recv(1024) print(f"来自客户端{addr[1]}的消息:{from_client_data.decode('utf-8')}") to_client_data = input(">>>").strip() conn.send(to_client_data.encode("utf-8")) except Exception: break conn.close() def _accept(): server = socket.socket() server.bind(("12 0.0.1",8080)) server.listen(5) while 1: conn,addr = server.accept() t = Thread(target=communicate,args=(conn,addr)) t.start() if __name__ == '__main__': _accept()
# 客户端 import socket client = socket.socket() client.connect(("12 0.0.1",8080)) while 1: to_server_data = input(">>>").strip() client.send((to_server_data.encode("utf-8"))) from_sever_data = client.recv(1024) print(f"来自服务端的消息:{from_sever_data.decode('utf-8')}") client.close()
26线程池 进程池
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor import os import time import random # print(os.cpu_count()) def task(n): print(f'{os.getpid()} 接客') time.sleep(random.randint(1,3)) if __name__ == '__main__': # 开启进程池 (并行(并行+并发)) # p = ProcessPoolExecutor() # 默认不写,进程池里面的进程数与cpu个数相等 # # # p.submit(task,1) # # p.submit(task,1) # # p.submit(task,1) # # p.submit(task,1) # # p.submit(task,1) # # p.submit(task,1) # # p.submit(task,1) # for i in range(20): # p.submit(task,i) # # 开启线程池 (并发) t = ThreadPoolExecutor() # 默认不写, cpu个数*5 线程数 # t = ThreadPoolExecutor(100) # 100个线程 for i in range(20): t.submit(task,i)
27同步调用 异步调用
- 阻塞:程序运行时,遇到io,程序挂起,cpu呗切走.
- 非阻塞:程序没有遇到IO,程序遇到IO但是我通过某种手段,让CPU强行运行我的程序.
- 同步:提交一个任务,自任务开始运行知道此任务结束(可能有IO),返回一个返回值之后,我在提交下一个任务.
- 异步:一次提交多个任务,然后我就直接执行下一行代码.
2 1同步调用
from concurrent.fufures import ProcessPoolExecutor,ThreadPoolExecutor import time,rangdom,os def task(i): print(f"{os.getpid()}开始任务") time.sleep(random.randint(1,3)) print(f"{os.getpid()}任务结束") return i if __name__ == "__main__": pool = ProcessPoolExecutor() for i in range(10): obj = pool.submit(task,i) print(f'任务结果:{obj.result()}') # obj是一个动态对象,返回的当前的对象的状态,有可能运行中,可能(就绪阻塞),还可能是结束了. # obj.result() 必须等到这个任务完成后,返回了结果之后,在执行下一个任务. pool.shutdown(wait==True) # shutdown: 让我的主进程等待进程池中所有的子进程都结束任务之后,在执行. 有点类似与join. # shutdown: 在上一个进程池没有完成所有的任务之前,不允许添加新的任务. # 一个任务是通过一个函数实现的,任务完成了他的返回值就是函数的返回值. print('===主')
2 2异步调用
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor import time import random import os def task(i): print(f'{os.getpid()}开始任务') time.sleep(random.randint(1,3)) print(f'{os.getpid()}任务结束') return i if __name__ == '__main__': # 异步调用 pool = ProcessPoolExecutor() l1 = [] for i in range(10): obj = pool.submit(task,i) l1.append(obj) pool.shutdown(wait=True) print(l1) for i in l1: print(i.result()) print('===主')
28异步调用 回调函数
浏览器工作原理, 向服务端发送一个请求,服务端验证你的请求,如果正确,给你的浏览器返回一个文件, 浏览器接收到文件,将文件里面的代码渲染成你看到的漂亮美丽的模样. 什么叫爬虫? 1. 利用代码模拟一个浏览器,进行浏览器的工作流程得到一堆源代码. 2. 对源代码进行数据清洗得到我想要数据.
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor import os,time,rangdom,requests def task(url): '''模拟的就是爬取多个源代码 一定有IO操作''' ret = requests.get(url) if ret.status_code == 200: return ret.text def parse(content): '''模拟对数据进行分析 一般没有IO''' return len(content) if __name__ == '__main__': """ '''串行 耗费时间长,不可取''' ret = task('http://www.baidu.com') print(parse(ret)) ret = task('http://www.JD.com') print(parse(ret)) ret = task('http://www.taobao.com') print(parse(ret)) ret = task('https://www.cnblogs.com/jin-xin/articles/745997 html') print(parse(ret)) """ # 开启线程池,并发并行的执行 url_list = [ 'http://www.baidu.com', 'http://www.JD.com', 'http://www.JD.com', 'http://www.JD.com', 'http://www.taobao.com', 'https://www.cnblogs.com/jin-xin/articles/745997 html', 'https://www.luffycity.com/', 'https://www.cnblogs.com/jin-xin/articles/9811379.html', 'https://www.cnblogs.com/jin-xin/articles/11245654.html', 'https://www.sina.com.cn/', ] pool = ThreadPoolExecutor(4) obj_list = [] for url in url_list: obj = pool.submit(task,url) obj_list.append(obj) pool.shutdown(wait=True) for res in obj_list: print(parse(res.result())) # 版本一: # 1. 异步发出10个任务,并发的执行,但是统一的接收所有的任务的返回值.(效率低,不能实时的获取结果) # 2. 分析结果流程是串行,影响效率. # for res in obj_list: # print(parse(res.result()))
版本二: 针对版本一的缺点2,改进,让串行编程并发或者并行. from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor import time import random import os import requests def task(url): '''模拟的就是爬取多个源代码 一定有IO操作''' ret = requests.get(url) if ret.status_code == 200: return parse(ret.text) def parse(content): '''模拟对数据进行分析 一般没有IO''' return len(content) if __name__ == '__main__': # 开启线程池,并发并行的执行 url_list = [ 'http://www.baidu.com', 'http://www.JD.com', 'http://www.JD.com', 'http://www.JD.com', 'http://www.taobao.com', 'https://www.cnblogs.com/jin-xin/articles/745997 html', 'https://www.luffycity.com/', 'https://www.cnblogs.com/jin-xin/articles/9811379.html', 'https://www.cnblogs.com/jin-xin/articles/11245654.html', 'https://www.sina.com.cn/', ] pool = ThreadPoolExecutor(4) obj_list = [] for url in url_list: obj = pool.submit(task, url) obj_list.append(obj) ''' # # 1 在开一个线程进程池,并发并行的处理. 再开一个线程进程池,开销大. # # 2 将原来的任务扩大, # 版本一: # 线程池设置4个线程, 异步发起10个任务,每个任务是通过网页获取源码, 并发执行, # 最后统一用列表回收10个任务, 串行着分析源码. # 版本二: # 线程池设置4个线程, 异步发起10个任务,每个任务是通过网页获取源码+数据分析, 并发执行, # 最后将所有的结果展示出来. # 耦合性增强了. # 并发执行任务,此任务最好是IO阻塞,才能发挥最大的效果 # '''
线程池设置4个线程, 异步发起10个任务,每个任务是通过网页获取源码, 并发执行,
当一个任务完成之后,将parse这个分析代码的任务交由剩余的空闲的线程去执行,你这个线程继续去处理其他任务.
如果进程池+回调: 回调函数由主进程去执行.
如果线程池+回调: 回到函数由空闲的线程去执行.
# 版本三: # 基于 异步调用回收所有任务的结果我要做到实时回收结果, # 并发执行任务每个任务只是处理IO阻塞的,不能增加新得功能. # 异步调用 + 回调函数 from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor import time import random import os import requests def task(url): '''模拟的就是爬取多个源代码 一定有IO操作''' ret = requests.get(url) if ret.status_code == 200: return ret.text def parse(obj): '''模拟对数据进行分析 一般没有IO''' print(len(obj.result())) if __name__ == '__main__': # 开启线程池,并发并行的执行 url_list = [ 'http://www.baidu.com', 'http://www.JD.com', 'http://www.JD.com', 'http://www.JD.com', 'http://www.taobao.com', 'https://www.cnblogs.com/jin-xin/articles/745997 html', 'https://www.luffycity.com/', 'https://www.cnblogs.com/jin-xin/articles/9811379.html', 'https://www.cnblogs.com/jin-xin/articles/11245654.html', 'https://www.sina.com.cn/', ] pool = ThreadPoolExecutor(4) for url in url_list: obj = pool.submit(task, url) obj.add_done_callback(parse)
异步 回调是一回事儿?
- 异步站在发布任务的角度, 站在接收结果的角度: 回调函数 按顺序接收每个任务的结果,进行下一步处理.
异步 + 回调:
- 异步处理的IO类型. 回调处理非IO
29线程队列
1.先进先出
import queue q = queue.Queue(3) q.put(1) q.put(2) q.put(3) print(q.get()) print(q.get()) print(q.get())
2.后进先出 LiFo 堆栈
q = queue.LiFoQueue(4) q.put(1) q.put(2) q.put("二狗") q.put("zq") print(q.get()) print(q.get()) print(q.get()) print(q.get())
3.优先级队列
q = queue.PriorityQueue(4) q.put((5, '元宝')) q.put((-2,'狗狗')) q.put((0, '李业')) q.put((6, '刚哥')) print(q.get()) print(q.get()) print(q.get()) print(q.get())
30event
一个线程监测服务器是否开始,
另个一线程判断如果开始了,则显示连接成功,此线程只尝试连接3次,1s 一次,如果超过3次,还没有连接成功,则显示连接失败.
from threading import Thread from threading import current_thread from threading import Event import time event = Event() def check(): print(f'{current_thread().name} 监测服务器是否开启...') time.sleep(4) event.set() print('服务器已经开启...') def connect(): count = 1 while not event.is_set(): if count == 4: print('连接次数过多,已断开') break event.wait(1) print(f'{current_thread().name} 尝试连接{count}次') count += 1 else: print(f'{current_thread().name} 连接成功...') t1 = Thread(target=check,) t2 = Thread(target=connect,) t1.start() t2.start()
31协程
一个线程并发的处理任务.
串行: 一个线程执行一个任务,执行完毕之后,执行下一个任务.
并行: 多个cpu执行多个任务, 4个cpu 执行4个任务.
并发: 一个cpu执行多个任务,看起来像是同时运行.
并发真正的核心: 切换并且保持状态.
多线程的并发: 3个线程处理10个任务,如果线程1处理的这个任务,遇到阻塞,cpu被操作系统切换到另一个线程,
一个线程能否并发的处理任务??? 一个线程处理三个任务.
单个cpu: 10个任务,让你给我并发的执行这个10个任务:
- 方式一:开启多进程并发执行, 操作系统切+保持状态.
- 方式二:开启多线程并发执行,操作系统切+保持状态.
- 方式三:开启协程并发的执行, 自己的程序 把控着
cpu 在3个任务之间来回切换+保持状态.对3详细解释: 协程他切换速度非常快,蒙蔽操作系统的眼睛,让操作系统认为cpu一直在运行你这一个线程(协程.)
协程方式最好,为什么?
- 开销小.
- 运行速度快.
- 协程会长期霸占cpu只执行我程序里面的所有任务.并发的本质:就是切换+保持状态.协程处理IO密集型, 计算密集型,还是串行好.什么是协程? 单个线程并发的处理多个任务. 程序控制协程的切换+保持状态.
协程的特点:- 必须在只有一个单线程里实现并发
- 修改共享数据不需加锁
- 用户程序里自己保存多个控制流的上下文栈(保持状态)
- 附加:一个协程遇到IO操作自动切换到其它协程
工作中:
一般在工作中我们都是进程+线程+协程的方式来实现并发,以达到最好的并发效果,如果是4核的cpu,一般起5个进程,每个进程中20个线程(5倍cpu数量),每个线程可以起500个协程,大规模爬取页面的时候,等待网络延迟的时间的时候,我们就可以用协程去实现并发。 并发数量 = 5 * 20 * 500 50000个并发,这是一般一个4cpu的机器最大的并发数。nginx在负载均衡的时候最大承载量就是5w个单线程里的这20个任务的代码通常会既有计算操作又有阻塞操作,我们完全可以在执行任务1时遇到阻塞,就利用阻塞的时间去执行任务2。。。。如此,才能提高效率,这就用到了Gevent模块。