参考:cnblogs.com/yuanchenqi/articles/6248025.html
一.操作系统
1.操作系统作用:
现代计算机系统是由一/多个处理器,主存,磁盘,打印机,键盘,鼠标,显示器,网络接口及各种其他输入输出设备组成的复杂系统,不可能掌握所有系统实现的细节,且管理优化这些部件是难度极大。所以需要为计算机安装一层软件,成为操作系统,为用户程序提供一个简单清晰的计算机模型并管理以以上所有设备
程序是运行在系统上的具有某种功能的软件,如浏览器
2.历史
(1)真空管与穿孔卡片(无操作系统)
过程:
将对应于程序和数据的已穿孔的纸带/卡片装入输入机
然后启动输入机把程序和数据输入计算机内存
接着通过控制台开关启动程序针对数据运行
计算完毕,打印机输出计算结果
用户取走结果并卸下纸带/卡片后,才允许下一个用户上机
注意点:
1 程序员需要在墙上的计时表上预约时间
2 同一时刻只有一个程序在内存中被CPU调用运行(串行的)
优点:程序员在申请的时间段内独享整个资源,即时的调试自己的程序,如果有bug可以当场处理,
缺点:对计算机提供商是一种浪费(大量空闲时间)
(2)晶体管和批处理系统
一代计算机的问题:人机交互过多(输入--->计算--->输出 输入--->计算--->输出 输入--->计算--->输出)
解决办法:把一堆人的输入攒成一大波输入,然后顺序计算
再把计算结果攒成一大波输出,这就是批处理系统
·这是有问题的,但是第二代计算没有解决
操作系统前身:
在收集了大约一个小时的批量作业之后,这些卡片被读入磁带
然后磁带被送到机房里并装到磁带上,然后磁带被送到机房里并装到磁带机上
随后操作员装入一个特殊的程序(现代操作系统的前身)
负责从磁带上读入第一个作业(job,一个或一组程序)并运行
其输出写到第二个磁带上,而且不打印
每个作业结束后,操作系统自动的从磁带上读入下一个作业并且运行
一整批作业全部结束后,操作员取下输入/输出磁带,将输入磁带换成下一批作业
并把输出磁带拿到一台1041机器上进行脱机(不与主计算机联机)打印
优点:批处理
缺点:仍然需要人介入;仍然是顺序计算--->IO阻塞
(3)集成电路芯片和多道程序设计
针对二代计算机的两个主要问题
开发出同时的外部设备连机操作(SPOOLING技术),解决交互问题:
卡片被拿到机房后能够很快的将作业从卡片读入磁盘
于是任何时刻当一个作业结束时,操作系统就能将一个作业从磁带读出
装进空出来的内存区域运行,这种技术叫做同时的外部设备联机操作
该技术同时用于输出
当采用了这种技术后,就不在需要IBM1401机了,也不必将磁带搬来搬去了
开发出多道程序设计,用于解决顺序执行的问题:
在7094机上(程序运行的机器)
若当前作业因等待磁带或等待其他IO操作而暂停
CPU就处于休闲状态直至IO操作完成
对于CPU密集的科学计算,IO操作少,浪费时间不明显
对于商业数据处理,IO等待能到达80%~90%,所以必须解决CPU浪费的现象。
解决方案:将内存分为几个部分,每一部分存放不同的作业,如图1-5
当一个作业等待IO完成时,另一个作业可以使用CPU
内存中放足够的作业,则CPU的利用率能接近100%
第三代计算机适合大型科学计算和繁忙的商务数据处理,但本质上仍是一个批处理系统
虽然解决了诸如以上问题,但多个作业必须在全部运行结束后,才能得到结果
从一个作业的提交到运算结果取回往往长达数小时,无法实时修改代码
为了满足对实时性的要求,出现了分时操作系统
分时操作系统:多个联机终端+多道技术
20个客户端同时加载到内存,有17在思考,3个在运行
cpu就采用多道的方式处理内存中的这3个程序
由于客户提交的一般都是简短的指令而且很少有耗时长的
索引计算机能为许多用户提供快速的交互式服务
所有的用户都以为自己独享了计算机资源
(4)个人计算机
随着大规模集成电路的发展,每平方厘米的硅片芯片上可以集成数千个晶体管,个人计算机的时代就此到来
二.进程和线程:均为抢占式
单个CPU核心不存在并行,只存在并发->概念见后
假如有两个程序A和B
程序A在执行到一半的过程中,需要读取大量的数据输入(I/O操作)
而此时CPU只能等待任务A读取完数据才能继续执行,这样浪费CPU资源
在程序A读取数据的过程中,让程序B去执行
当程序A读取完数据之后让程序B暂停,然后让程序A继续执行
这里有一个关键词:切换
既然是切换,就涉及到状态的保存/恢复
加上程序A与程序B所需要的系统资源是不一样的
就需要有一个东西去记录A和B需要什么资源/怎样识别A和B等
所以就有了一个叫进程的抽象
1.进程:程序!=进程
(1)概念:一个程序在一个数据集上的一次动态执行过程
(2)进程一般由程序、数据集、进程控制块三部分组成
·数据集则是程序在执行过程中所需要使用的资源
·进程控制块用来记录进程的外部特征,描述进程的执行变化过程
系统可以利用它来控制和管理进程,它是系统感知进程存在的唯一标志
(3)进程切换:遇到IO操作;时间轮询(程序间每隔一定时间自动切换进程,即使没有IO操作,直到再次回到本程序)
(4)是操作系统分配资源的最小单位
2.线程:可理解为进程中的微进程,共享进程中资源集
假设一个文本程序需要键盘输入,将内容显示在屏幕上,还需要保存信息到硬盘中
若只有一个进程则同一时间只能干一件事(如保存时不能通过键盘输入)
若有多个进程,每个进程负责一个任务
进程A负责接收键盘输入的任务,进程B负责将内容显示在屏幕上的任务,进程C负责保存内容到硬盘中的任务
这里进程A,B,C间的协作涉及到了进程通信问题
而且有共同都需要拥有的东西---文本内容,不停的切换造成性能上的损失
有一种机制,可以使任务A,B,C共享资源
这样上下文切换需要保存/恢复的内容少了,同时减少通信带来的性能损耗
这种机制就是线程。
(1)概念:又叫轻量级进程,是一个基本的CPU执行单元,也是程序执行过程中的最小单元
(2)由线程ID,程序计数器,寄存器集合,堆栈组成
(3)提高操作系统的并发性能;降低上下文切换的消耗;突破一个进程只能干一样事的缺陷,使进程内并发成为可能
(4)线程没有自己的系统资源
3.线程进程的关系区别
(1)一个程序至少有一个进程,一个进程至少有一个线程(进程可以理解成线程的容器)
(2)进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率
(3)线程在执行过程中与进程还是有区别的:每个独立线程有一个程序运行的入口,顺序执行序列和程序的出口;但线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制
(4)进程是具有一定独立功能的程序在某个数据集合上的一次运行,是系统进行资源分配和调度的最小独立单位
线程是进程的一个实体,是CPU调度和分派的基本单位,是比进程更小的能独立运行的基本单位
线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但可与同属一个进程的其他的线程共享进程所拥有的全部资源
一个线程可以创建和撤销另一个线程;同一个进程中的多个线程可以并发执行
三.python的线程与threading模块
1.线程为抢占式
2.线程的两种调用方式
threading模块建立在thread 模块之上
thread模块以低级原始的方式来处理和控制线程,而threading模块通过对thread进行二次封装,提供了更方便的api来处理线程
(1)直接调用:
import threading #线程
import time
def sayhi(num): #定义每个线程要运行的函数
print("running on number:%s" %num)
time.sleep(3)
if __name__ == '__main__':
t1 = threading.Thread(target=sayhi,args=(1,)) #生成一个线程实例
t2 = threading.Thread(target=sayhi,args=(2,)) #生成另一个线程实例
t1.start() #启动线程
t2.start() #启动另一个线程
print(t1.getName()) #获取线程名
print(t2.getName())
import threading,time
def Hi(num):
print(num)
time.sleep(3)
if __name__ == '__main__':
t1 = threading.Thread(target=Hi,args=(1,)) #创建子线程对象t1
t1.start()
t2 = threading.Thread(target=Hi,args=(2,)) #创建子线程对象t2
t2.start()
print('ending') #创建一个主线程
#结果:1,2,ending同时被打印,3s后程序结束
import threading,time
def music():
print('begin to listen %s't%time.ctime())
time.sleep(3)
print('stop to listen %s't%time.ctime())
def game():
print('begin to play %s't%time.ctime())
time.sleep(5)
print('stop to play %s't%time.ctime())
if __name__ == '__main__':
t1=threading..Thread(target=music)
t1.start()
t2=threading.Thread(target=game)
t2.start()
#结果:begin to listen xxx与begin to play xxx同时被打印
3s后打印stop to listen
再2s后打印stop to play,共用时5s
(2)继承式调用:
import threading,time
class MyThread(threading.Thread):
def __init__(self,num):
threading.Thread.__init__(self)
self.num = num
def run(self):#定义每个线程要运行的函数
print("running on number:%s" %self.num)
time.sleep(3)
if __name__ == '__main__':
t1 = MyThread(1)
t2 = MyThread(2)
t1.start()
t2.start()
print("ending......")
3.threading.thread的实例方法
(1)join():在子线程完成运行前,其父线程将一直被阻塞
import threading,time
def music():
print('begin to listen %s't%time.ctime())
time.sleep(3)
print('stop to listen %s't%time.ctime())
def game():
print('begin to play %s't%time.ctime())
time.sleep(5)
print('stop to play %s't%time.ctime())
if __name__ == '__main__':
t1=threading..Thread(target=music)
t2=threading.Thread(target=game)
t1.start()
t2.start()
t1.join()
t2.join()
print('ending')
#结果:begin to listen xxx与begin to play xxx同时被打印
# 3s后打印stop to listen
# 再2s后打印stop to play与ending,共用时5s
#没有join()则ending与begin同时被打印
#去除ts.join()则打印stop to listen后立即打印ending
(2)setDaemon(True):将线程声明为守护线程,必须在start()方法调用之前设置,如果不设置为守护线程程序会被无限挂起
这个方法基本和join()是相反的
执行一个主线程,如果主线程又创建了一个子线程,主线程和子线程分别运行
那么当主线程完成想退出时,会检验子线程是否完成
如果子线程未完成,则主线程会等待子线程完成后再退出
但是有时需要:只要主线程完成,不管子线程是否完成,都要和主线程一起退出
这时就可以用setDaemon()
import threading,time
from time import ctime,sleep
def ListenMusic(name):
print ("Begin listening to %s. %s" %(name,ctime()))
sleep(3)
print("end listening %s"%ctime())
def RecordBlog(title):
print ("Begin recording the %s! %s" %(title,ctime()))
sleep(5)
print('end recording %s'%ctime())
threads = []
t1 = threading.Thread(target=ListenMusic,args=('水手',))
t2 = threading.Thread(target=RecordBlog,args=('python线程',))
threads.append(t1)
threads.append(t2)
if __name__ == '__main__':
#t1.setDaemon(True) #无效(t2结束时主线程才结束,此时t1已结束)
for t in threads:
t.setDaemon(True) #注意:一定在start之前设置
#t1,t2均被设为守护线程,有主线程一同结束
t.start()
print ("all over %s" %ctime())
(3)其他方法:
run():线程被cpu调度后自动执行线程对象的run方法
start():启动线程活动(实际只是是线程就绪,等待OS调用CPU执行)
isAlive():判断线程是否活动
getName():返回线程名(默认名为Thread-n,n表示第几个线程)
setName():设置线程名
#threading模块提供的一些方法:
threading.currentThread():返回当前的线程变量
threading.enumerate(): 返回一个包含正在运行的线程的list;正在运行指线程启动后,结束前,不包括启动前和终止后的线程
threading.activeCount(): 返回正在运行的线程数,与 en(threading.enumerate())结果相同
四.并发与并行,同步与异步,阻塞与非阻塞
1.并发:一个系统具有处理多个任务的能力(如时间轮询)
2.并行:一个系统具有同时处理多个任务的能力(如多核CPU)
3.同步:所有的操作都执行完,才返回给用户;即写完数据库之后,再响应用户;也即方法被调用时,直接得到最终结果
4.异步:不用等所有操作都做完,就相应用户请求;即先响应用户请求,然后慢慢去写数据库;也即方法被调用时,不直接得到最终结果
当进程执行IO(等待外部数据)时---等--->同步
---不等,去执行其他任务,等到数据接收成功,再回来处理--->异步
打饭模型---打饭不打好不走开,直到打饭给我后才离开--->同步
---不会一直等着,时不时的过来看看,打完了把饭拿走
5.阻塞:方法调用时,不立即返回
6.非阻塞:方法调用时,立即返回
五.锁
1.python的全局解释锁(GIL):
(1)概念:对一个进程来说,无论开启多少个线程/有多少个CPU核心,Python在执行的时候同一时刻只允许一个线程运行,即使有多个CPU核心
- In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)
(2)解决:将线程分到不同进程中(但进程的资源开销大)—>多进程+协程—>换一种语言(如C语言)
(3)与Python语法无关,为解释器层面限制
(4)GIL使Python开启多线程时会不断切换线程,且无法利用多个CPU核心,使执行时间大于串行时用时
2.任务:
(1)IO密集型:适合Python多线程/多进程+协程
sleep()相当于IO操作
如爬虫(web crawler),请求网页,读写文件
(2)计算密集型:不适合使用Python
如科学计算,视频解码
3.同步锁Lock(也叫互斥锁):在指定操作进行时不允许切换线程
import time,threading
def addNum():
global num #在每个线程中都获取这个全局变量
temp=num
time.sleep(0.1)
num =temp-1 #对此公共变量进行-1操作
num = 100 #设定一个共享变量
thread_list = []
for i in range(100):
t = threading.Thread(target=addNum)
t.start()
thread_list.append(t)
for t in thread_list: #等待所有线程执行完毕
t.join()
print('final num:', num )
#final num: 99 #时间轮询较快,切换到下一个线程时,还未修改num
#最终结果与sleep()的时间有关,sleep(0.0001)时结果为91
#sleep()时间相同时,每次结果也可能不同
多个线程都在同时操作同一个共享资源,造成资源破坏,可以通过同步锁来解决(join会造成串行,失去所线程的意义)
import time,threading
def addNum():
global num
R.acquire() #启用同步锁
temp=num
time.sleep(0.1)
num =temp-1
R.release() #停止同步锁
num = 100
thread_list = []
R=threading.Lock() #产生一个同步锁Lock
for i in range(100):
t = threading.Thread(target=addNum)
t.start()
thread_list.append(t)
for t in thread_list:
t.join()
print('final num:', num )
#final num: 0
在Lock.acquire()与Lock.release()间不会切换线程,这一部分变为单线程执行
4.线程死锁与递归锁
(1)在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁:因为系统判断这部分资源都正在使用,所有这两个线程在无外力作用下将一直等待下去
import threading,time
class myThread(threading.Thread):
def doA(self):
lockA.acquire() #线程1开始执行,另外4个等着,直到lockA.release()
print(self.name,"gotlockA",time.ctime())
time.sleep(3)
lockB.acquire() #线程2等待线程1 lockB.release
print(self.name,"gotlockB",time.ctime())
lockB.release()
lockA.release()
def doB(self):
lockB.acquire() #线程1开始执行,此时线程2再执行doA
print(self.name,"gotlockB",time.ctime())
time.sleep(2)
lockA.acquire() #线程1等待线程2 lockA.release
print(self.name,"gotlockA",time.ctime())
lockA.release()
lockB.release()
def run(self):
self.doA()
self.doB()
if __name__=="__main__":
lockA=threading.Lock()
lockB=threading.Lock()
threads=[]
for i in range(5):
threads.append(myThread())
for t in threads:
t.start()
for t in threads:
t.join()#等待线程结束,后面再讲
#结果:
#Thread-1 gotlockA Mon Feb 17 16:04:43 2020
#Thread-1 gotlockB Mon Feb 17 16:04:46 2020
#Thread-1 gotlockB Mon Feb 17 16:04:46 2020
#Thread-2 gotlockA Mon Feb 17 16:04:46 2020
#永不结束
(2)解决:使用递归锁threading.RLock()
import threading,time
class myThread(threading.Thread):
def doA(self):
lockR.acquire() #线程1开始执行,另外4个等着,直到lockA.release()
print(self.name,"gotlockA",time.ctime())
time.sleep(3)
lockR.acquire() #线程2等待线程1 lockB.release
print(self.name,"gotlockB",time.ctime())
lockR.release()
lockR.release()
def doB(self):
lockR.acquire() #线程1开始执行,此时线程2再执行doA
print(self.name,"gotlockB",time.ctime())
time.sleep(2)
lockR.acquire() #线程1等待线程2 lockA.release
print(self.name,"gotlockA",time.ctime())
lockR.release()
lockR.release()
def run(self):
self.doA()
self.doB()
if __name__=="__main__":
lockR=threading.RLock()
threads=[]
for i in range(5):
threads.append(myThread())
for t in threads:
t.start()
for t in threads:
t.join()#等待线程结束,后面再讲
#结果:
#Thread-1 gotlockA Mon Feb 17 16:39:46 2020
#Thread-1 gotlockB Mon Feb 17 16:39:49 2020
#Thread-1 gotlockB Mon Feb 17 16:39:49 2020
#Thread-1 gotlockA Mon Feb 17 16:39:51 2020
#Thread-3 gotlockA Mon Feb 17 16:39:51 2020
#Thread-3 gotlockB Mon Feb 17 16:39:54 2020
#Thread-3 gotlockB Mon Feb 17 16:39:54 2020
#Thread-3 gotlockA Mon Feb 17 16:39:56 2020
#Thread-5 gotlockA Mon Feb 17 16:39:56 2020
#Thread-5 gotlockB Mon Feb 17 16:39:59 2020
#Thread-5 gotlockB Mon Feb 17 16:39:59 2020
#Thread-5 gotlockA Mon Feb 17 16:40:01 2020
#Thread-4 gotlockA Mon Feb 17 16:40:01 2020
#Thread-4 gotlockB Mon Feb 17 16:40:04 2020
#Thread-4 gotlockB Mon Feb 17 16:40:04 2020
#Thread-4 gotlockA Mon Feb 17 16:40:06 2020
#Thread-2 gotlockA Mon Feb 17 16:40:06 2020
#Thread-2 gotlockB Mon Feb 17 16:40:09 2020
#Thread-2 gotlockB Mon Feb 17 16:40:09 2020
#Thread-2 gotlockA Mon Feb 17 16:40:11 2020
import threading,time
class Account:
def __init__(self, _id, balance):
self.id = _id
self.balance = balance
self.lock = threading.RLock()
def withdraw(self, amount):
with self.lock:
self.balance -= amount
def deposit(self, amount):
with self.lock:
self.balance += amount
def drawcash(self, amount):#lock.acquire中嵌套lock.acquire的场景
with self.lock:
interest=0.05
count=amount+amount*interest
self.withdraw(count)
def transfer(_from, to, amount):
#锁不可以加在这里 因为其他的其它线程执行的其它方法在不加锁的情况下数据同样是不安全的
_from.withdraw(amount)
to.deposit(amount)
alex = Account('alex',1000)
yuan = Account('yuan',1000)
t1=threading.Thread(target = transfer, args = (alex,yuan, 100))
t1.start()
t2=threading.Thread(target = transfer, args = (yuan,alex, 200))
t2.start()
t1.join()
t2.join()
print('>>>',alex.balance)
print('>>>',yuan.balance)
为支持在同一线程中多次请求同一资源,python提供了"可重入锁"threading.RLock:其内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,使得资源可被多次acquire;直到一个线程所有的acquire都被release,其他的线程才能获得资源(counter>0时只有本线程可以acquire)