系统基础
操作系统的作用
隐藏复杂的硬件接口,提供良好的抽象接口。
管理、调度进程,并且将多个进程对硬件的竞争变得有序。
多道技术
产生背景:针对单核,实现并发(现在的主机一般是多核,那么每个核都会利用多道技术,但是核与核之间没有使用多道技术切换这么一说,一个程序io阻塞,会等到io结束再重新调度)
时间上的复用(复用一个cpu的时间片)+空间上的复用(如内存中同时有多道程序)
特点:多道----主存中存放两道或两道以上的程序;宏观上 并行----在一个时间段,它们都在同时执行,都处于执行的开始点和结束点之间;微观上串行----在某一时刻,他们在同一台计算机上交替、轮流、穿插地执行。
进程
进程实体:程序段、数据段、进程控制块(PCB)。
**进程:**进程就是进程实体的一次执行过程。
多进程(任务)并发执行:比如你现在使用电脑,一边在用浏览器上网,一边在听MP3,一边在用Word赶作业,对于单核CPU来说,执行多任务是这样的:操作系统轮流让各个任务交替执行,任务1执行0.001秒,切换到任务2,任务2执行0.001秒,再切换到任务3,执行0.001秒……这样反复执行下去。表面上看,每个任务都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。
进程并行:对于多核CPU的系统,如果是4核可以有四个任务并行执行。并行执行就是真正的同时执行,所以并行是并发的子集。
总结:现在的操作系统很多任务执行都是并行+并发执行。
线程
有些进程还不止同时干一件事,比如Word,它可以同时进行打字、拼写检查、打印等事情。在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)。
线程的出现是为了降低上下文切换的消耗,提高系统的并发性,并突破一个进程只能干一样事的缺陷,使到进程内并发成为可能。
进程与线程关系
进程是最小的资源管理单元,线程是最小的执行单元,而进程由至少一个线程组成。如何调度进程和线程,完全由操作系统决定,程序自己不能决定什么时候执行,执行多长时间。
资源分配给进程,同一进程的所有线程共享该进程的所有资源。
python中threading模块
python中要在一个程序中开多个线程需要使用threading模块。
线程创建的两种方式
方式一:直接创建
import threading
import time
def foo(n):
print(">>>>>>>>%s" %n)
time.sleep(n)
print(threading.active_count()) #打印当前线程数目,结果为3,sleep后t2早已激活,还有一个是主线程
def bar(n):
print(">>>>>>>>%s" % n)
time.sleep(n)
t1 = threading.Thread(target=foo,args=(2,)) #创建一个线程对象,执行foo任务,元组形式传入参数(2,)
t1.start() #激活线程对象(调用run方法)
print(threading.active_count()) ##打印当前线程数目,结果为2,因为t2还没有激活,还有一个是主线程
t2 = threading.Thread(target=bar,args=(5,))
t2.start()
print("ending")
方式二:从Thread继承,并重写run()
import threading
import time
class MyThread(threading.Thread):
def __init__(self,num): #重写init方法,调用父类的init
# threading.Thread.__init__(self)
super(MyThread,self).__init__()
self.num=num
def run(self): #重写run方法
print("running on number:%s" %self.num)
time.sleep(3)
t1=MyThread(1)
t2=MyThread(21)
t1.start()
t2.start()
print("ending")
Thread类的实例方法
# start(): 激活线程
# join():在子线程完成运行之前,这个子线程的父线程将一直被阻塞。
# setDaemon(True):
'''
将线程声明为守护线程,必须在start() 方法调用之前设置,如果不设置为守护线程程序会被无限挂起。
当我们在程序运行中,执行一个主线程,如果主线程又创建一个子线程,主线程和子线程 就分兵两路,分别运行,那么当主线程完成
想退出时,会检验子线程是否完成。如果子线程未完成,则主线程会等待子线程完成后再退出。但是有时候我们需要的是只要主线程
完成了,不管子线程是否完成,都要和主线程一起退出,这时就可以 用setDaemon方法啦'''
# isAlive(): 返回线程是否活动的。
# getName(): 返回线程名。
# setName(): 设置线程名。
threading模块提供的一些方法:
# threading.currentThread(): 返回当前的线程变量。
# threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
# threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
GIL锁
Python中的线程是操作系统的原生线程,Python虚拟机使用一个全局解释器锁(Global Interpreter Lock)来互斥线程对Python虚拟机的使用。为了支持多线程机制,一个基本的要求就是需要实现不同线程对共享资源访问的互斥,所以引入了GIL。要知道GIL锁是在进程上的,即一个进程内线程无法在多核系统并行执行。最重要的是Cpython才有的GIL锁,大多数都是默认Cpython
**GIL:**在一个线程拥有了解释器的访问权之后,其他的所有线程都必须等待它释放解释器的访问权,即使这些线程的下一条指令并不会互相影响。 在调用任何Python C API之前,要先获得GIL
GIL缺点:多处理器退化为单处理器;无法利用多核CPU实现多线程优点:避免大量的加锁解锁操作
线程同步锁
为什么要有同步锁呢?答案是可以解决数据安全一致性问题。如下示例:
#未在数据处理段加锁,想得到最终num=0,执行结果却是94
import threading
import time
num = 100
def foo():
global num
temp = num
time.sleep(0.0001)
num = temp - 1
l = []
for i in range(100):
threading.Thread(target=foo,args=()).start()
# l.append(t)
print(threading.active_count())
while True:
if threading.active_count() == 1:
break
# for x in l:
# x.join()
print(num)
#在数据处理段加锁之后得到理想结果num = 0
import threading
import time
num = 100
lock = threading.Lock()
def foo():
global num
lock.acquire()
temp = num
time.sleep(0.0001)
num = temp - 1
lock.release()
l = []
for i in range(100):
threading.Thread(target=foo,args=()).start()
# l.append(t)
print(threading.active_count())
while True:
if threading.active_count() == 1:
break
# for x in l:
# x.join() #等待所有线程执行完毕
print(num)
当给线程处理内容加锁之后,只有当锁release释放之后其它线程才能去acquire获得这把锁,这样就能保证存取操作的唯一性, 从而保证同一时刻只有一个线程对共享数据存取。一般同步锁使用形式:
import threading
lock=threading.Lock()
lock.acquire()
'''
对公共数据的操作
'''
lock.release()
死锁与递归锁
所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
死锁
#死锁示例:
import threading
import time
lockA = threading.RLock()
lockB = threading.RLock()
class Mythread(threading.Thread):
def __init__(self):
super().__init__()
def run(self):
self.foo()
self.bar()
def foo(self):
lockA.acquire()
print('lock A',self.name)
lockB.acquire()
print('lock B',self.name)
lockB.release()
# time.sleep(0.2)
lockA.release()
# time.sleep(0.2)
def bar(self):
lockB.acquire()
print('lock B',self.name)
lockA.acquire()
print('lock A',self.name)
lockA.release()
lockB.release()
for i in range(10):
t = Mythread()
t.start()
#上面这段程序就可能出现死锁,当然也有几率不会出现死锁,出现死锁的情况就是这样:
当线程1的foo释放完B锁后,B锁被线程1的bar函数抢到,然后当线程1
释放A锁后,A锁被线程2的foo函数抢到了,这样就会出现死锁。
lock A Thread-1
lock B Thread-1
lock B Thread-1
lock A Thread-2
递归锁解决死锁现象
import threading
import time
Rlock = threading.RLock()
class Mythread(threading.Thread):
def __init__(self):
super().__init__()
def run(self):
self.foo()
self.bar()
def foo(self):
Rlock.acquire()
print('lock A',self.name)
Rlock.acquire()
print('lock B',self.name)
Rlock.release()
# time.sleep(0.2)
Rlock.release()
# time.sleep(0.2)
def bar(self):
Rlock.acquire()
print('lock B',self.name)
Rlock.acquire()
print('lock A',self.name)
Rlock.release()
Rlock.release()
for i in range(10):
t = Mythread()
t.start()
在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁。
信号量锁Semaphore
Semaphore管理一个内置的计数器,
每当调用acquire()时内置计数器-1;
调用release() 时内置计数器+1;
计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。
信号量锁示例:
import threading
import time
semaphore = threading.Semaphore(5) #设置同时可以有5个线程可以获得信号量锁
def func():
if semaphore.acquire():
print (threading.currentThread().getName() + ' get semaphore')
time.sleep(2)
semaphore.release()
for i in range(20):
t1 = threading.Thread(target=func)
t1.start()
#最好执行结果,每两秒并发执行5个线程任务,即每2秒打印5次