操作系统的作用
1.隐藏丑陋复杂的硬件接口,提供良好的抽象接口;
2.管理、调度进程,并且将多个进程对硬件的竞争变得有序。
并发并行与同步异步的概念
-
并发:是系统具有处理多个任务的能力;单核CPU快速切换可以实现并发;
-
并行:是系统具有同时处理多个任务的能力;单核CPU无法并行;
并行是并发的一个子集;
-
同步:当进程执行中到一个IO(等待外部数据)的时候,---------等:同步 例如打电话
-
异步: 当进程执行中到一个IO(等待外部数据)的时候,--------不等:一直等到数据接收成功后,再回来处理
一、进程
进程的三种状态
运行态
阻塞态
就绪态
进程的概念
- 任务管理器里面,我们能够看到进程;
- 程序本质是一段代码,运行起来才是一个进程;
- 进程本质上是一段程序的运行过程;专业的说进程是一个程序在数据集上的一次动态执行过程;数据集就是程序执行需要调动的资源;
- 进程一般由程序,数据集,进程控制块三部分组成;例如一个厨师要做一个蛋糕,食谱就是程序,食材就是数据集,
- 之所以有进程,是我们想让多个程序同时跑起来;
- 一个单核CPU只会在不同程序间切换,绝对不会并行,并行就是两个程序同时执行,只是切换速度很快,我们感觉到两个程序是同时执行;双核才会并行程序;
创建进程的两种方式
1、创建进程对象
2、创建类继承
from multiprocessing import Process
import time
def f(name):
time.sleep(1)
print('hello', name, time.ctime())
if __name__ == '__main__':
p_list = []
for i in range(3):
p = Process(target=f, args=('shi',))
p_list.append(p)
p.start()
for i in p_list:
i.join()
print('end')
from multiprocessing import Process
import time
class MyProcess(Process):
def __init__(self):
super(MyProcess, self).__init__()
def run(self):
time.sleep(1)
print('hello', self.name, time.ctime())
if __name__ == '__main__':
p_list = []
for i in range(3):
p = MyProcess()
p_list.append(p)
p.start()
for i in p_list:
i.join()
print('end')
僵尸进程与孤儿进程
僵尸进程:子进程已经执行完,但是父进程还没有执行完,子进程不会消失,此状态为僵尸进程。
所有的进程都会经历僵尸进程的阶段。
孤儿进程:子进程还没有执行完,父进程就死掉了,此时子进程的父进程就是操作系统的一个公共父进程。
类似于操作系统有一个孤儿院,来作为父进程死掉的子进程的父进程。
join方法在进程中的应用
子进程调用join()方法,在程序这一行,主进程暂停,等待这个子进程执行完,再继续执行主进程。
from multiprocessing import Process
import time
def f(name):
time.sleep(1)
print('hello', name, time.ctime())
if __name__ == '__main__':
p = Process(target=f, args=('shi',))
p.start()
p.join()
print('end')
Process对象的其它方法和属性
构造方法:
1、target 要执行的方法
2、name 进程名
3、args/kwargs 要传入方法的参数
实例方法:
is_alive() 返回进程是否正在运行
join([timeout]) 阻塞当前上下文环境的进程,知道调用此方法的进程终止或达到指定的timeout(可选参数)。
start() 向操作系统发送一个开启进程的信号;进程准备就绪,等待CPU调度
run() start调用run方法
terminate() 不管任务是否完成,立即停止工作进程
属性:
daemon 守护进程, 和线程setDaemon功能一样
name 进程名字
pid 进程号
ppid 父进程号
def info(title):
print('title:', title)
print('parent process id:', os.getppid())
print('process id:', os.getpid())
if __name__ == '__main__':
info('shi')
p = Process(target=info, args=('qian',))
p.start()
p.join()
print('end')
守护进程
如果一个子进程要随着子进程的结束而结束,那么就设置这个子进程为守护进程;
也就是说当主进程结束之后,子进程没有存在的意义了,那我们就设置这个子进程为守护进程。
from multiprocessing import Process
import time
def f(name):
time.sleep(1)
print('hello', name, time.ctime())
if __name__ == '__main__':
p = Process(target=f, args=('shi',))
p.daemon = True # 设置守护进程必须在start之前
p.start()
print('end')
互斥锁(进程同步)
进程里的互斥锁,也叫 进程同步,也叫 同步锁
from multiprocessing import Process, Lock
import time
def f(i, lock):
lock.acquire()
print(i)
time.sleep(1)
print(i, '==')
lock.release()
if __name__ == '__main__':
lock = Lock() # 创建一把锁
for i in range(10):
p = Process(target=f, args=(i,lock)) # 保证所有的子进程用的是同一把锁,就把锁作为参数传进去
p.start()
多进程模拟抢票
from multiprocessing import Process, Lock
import json
# with open('db', 'w', encoding='utf-8') as f:
# json.dump({'count': 1}, f)
def search(name):
dic = json.load(open('db', 'r', encoding='utf-8'))
print('<%s>剩余票数:%s' % (name, dic['count']))
def get(name):
dic = json.load(open('db', 'r', encoding='utf-8'))
if dic['count'] > 0:
dic['count'] -= 1
json.dump(dic, open('db', 'w', encoding='utf-8'))
print('<%s>买票成功' % name)
def task(name, lock):
search(name)
lock.acquire()
get(name)
lock.release()
if __name__ == '__main__':
lock = Lock()
for i in range(10):
p = Process(target=task, args=(i, lock,))
p.start()
join和互斥锁的区别
join和互斥锁都是将并发变成串行,保证数据的安全性
join():(在start之后紧跟join) 将子进程所有代码变成串行,跟不开启进程一样。
互斥锁:将共享数据的一部分代码变成串行。
进程中的队列
每一个子进程的内存空间是独立的,但是可以共享硬盘上的数据。
上面模拟抢票中,每一个子进程都要去硬盘读取数据,效率低,而且需要自己枷锁处理;
那么multiprocessing模块提供了队列,在内存中共享数据,而且自动枷锁。
先来看看什么是队列
from multiprocessing import Queue
q = Queue(3)
q.put('s')
q.put(1)
q.put([1, 2])
print(q.full())
q.get()
q.get()
q.get()
print(q.empty())
多进程的生产者消费者模型
from multiprocessing import Queue, Process
import time
def produce(name, q):
for i in range(10):
print('厨师<%s>生产了%s个包子' % (name, name+str(i)))
q.put(name+str(i))
time.sleep(0.5)
def consumers(name, q):
while True:
time.sleep(1)
res = q.get()
if res == None:break
print('顾客<%s>吃了%s包子' % (name, res))
if __name__ == '__main__':
q = Queue()
p1 = Process(target=produce, args=('厨师A', q,))
p2 = Process(target=produce, args=('厨师B', q,))
p3 = Process(target=produce, args=('厨师C', q,))
c1 = Process(target=consumers, args=('顾客A', q,))
c2 = Process(target=consumers, args=('顾客B', q,))
p1.start()
p2.start()
p3.start()
c1.start()
c2.start()
p1.join()
p2.join()
p3.join()
q.put(None) # 向消费者发一个信号,说包子做完了,有几个消费者就发几个信号
q.put(None)
print('主')
二、线程
线程的概念
1、线程是最小的执行单元(实例),可以理解为进程内再开进程,即微进程;
2、一个程序肯定至少有一个进程,一个进程至少有一个线程,即主线程;
3、进程在执行过程中拥有独立的内存单元,多个线程共享这片内存资源,线程不可能在进程外;
4、一个线程可以创建和撤销另一个线程,同一个进程中的线程可以并发执行;
比如开发一个文本编辑器,至少有三个功能,接收、显示和保存,那么这三个功能很明显要共享用户输入的数据,而且要并发执行,
所以就必须用到多线程。
开启线程的两种方式
1、创建对象
2、重写类
import threading,time
def music():
print('begin to listen %s'%time.ctime())
time.sleep(3)
print('stop to listen %s'%time.ctime())
def game():
print('begin to game %s'%time.ctime())
time.sleep(5)
print('stop to game %s'%time.ctime())
if __name__ == '__main__':
t1= threading.Thread(target=music) #创建一个线程对象
t1.start()
t2= threading.Thread(target=game) #创建一个线程对象
t2.start()
print('ending-----------')
from threading import Thread
import time
class MyThread(Thread):
def __init__(self):
super(MyThread, self).__init__()
def run(self):
time.sleep(1)
print('hello', self.name, time.ctime())
if __name__ == '__main__':
t_list = []
for i in range(3):
t = MyThread()
t_list.append(t)
t.start()
print('end')
进程与线程的区别
1、开进程的开销远大于开线程
2、统一进程内的线程共享该进程的地址空间
3、瞅一眼pid
from threading import Thread
import os
def music():
print('begin to listen %s'%time.ctime())
time.sleep(3)
print('stop to listen %s'%time.ctime())
print(os.getpid())
def game():
print('begin to game %s'%time.ctime())
time.sleep(5)
print('stop to game %s'%time.ctime())
print(os.getpid())
if __name__ == '__main__':
t1= Thread(target=music) #创建一个线程对象
t1.start()
t2= Thread(target=game) #创建一个线程对象
t2.start()
print('ending-----------', os.getpid())
join在线程中的应用
join的线程不执行完,主线程暂停住,等join线程执行完,主线程在执行。不影响其他子线程的执行
import threading,time
def music():
print('begin to listen %s'%time.ctime())
time.sleep(3)
print('stop to listen %s'%time.ctime())
def game():
print('begin to game %s'%time.ctime())
time.sleep(5)
print('stop to game %s'%time.ctime())
if __name__ == '__main__':
t1= threading.Thread(target=music) #创建一个线程对象
t2= threading.Thread(target=game) #创建一个线程对象
t1.start()
t2.start()
t1.join() #t1和t2不执行完,主线程不执行
t2.join()
print('ending----------')
此段代码执行后,等t1,t2执行完,主线程才执行。
Thread对象的其他属性或方法
Thread实例对象的方法
isAlive(): 返回线程是否活动的。
getName(): 返回线程名。
setName(): 设置线程名。
threading模块提供的一些方法:
threading.currentThread(): 返回当前的线程变量。
threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
守护线程
在start之前设置
子线程是否要守护主线程(主线程结束后,子线程立马结束,不在继续执行)
t1.setDaemon(True)
t1.daemon = True
from threading import Thread
import time
def music():
print('begin to listen %s'%time.ctime())
time.sleep(3)
print('stop to listen %s'%time.ctime())
def game():
print('begin to game %s'%time.ctime())
time.sleep(5)
print('stop to game %s'%time.ctime())
if __name__ == '__main__':
t1 = Thread(target=music) # 创建一个线程对象
t1.daemon = True
t1.start()
t2 = Thread(target=game) # 创建一个线程对象
# t2.daemon = True
t2.start()
print('ending-----------')
互斥锁(线程同步)
from threading import Thread, Lock
import time
num = 100
def sub():
global num
lock.acquire()
num1 = num
time.sleep(0.01)
num = num1-1
lock.release()
if __name__ == '__main__':
list1=list()
lock=Lock() #创建锁对象
for i in range(100):
t=Thread(target=sub)
t.start()
list1.append(t)
for t in list1:
t.join()
print(num)
GIL基本概念
全局解释锁
在一个进程内,无论你启用多少个线程,你有多少CPU,python在执行的时候会淡定的在同一时刻只允许一个线程执行
因为有GIL,所以同一时刻只能有单核CPU执行
解决方式: 多进程+协成
任务:
IO密集型 遇到IO,线程切换,会充分利用CPU (sleep等同于IO操作)
计算密集型 一直在使用CPU
对于IO密集型的任务:python的多线程是有意义的,,,可以采用多进程+协成
对于计算密集型的任务:python的多线程就不推荐了,可以用多进程,或者用别的语言
IO密集型任务多进程与多线程比较
from multiprocessing import Process
from threading import Thread
import threading
import os,time
def work():
time.sleep(2)
if __name__ == '__main__':
l = []
# print(os.cpu_count()) # 本机为4核
start = time.time()
for i in range(400):
p = Process(target=work) # 耗时2.94s多,大部分时间耗费在创建进程上
# p = Thread(target=work) # 耗时2.032s多
l.append(p)
p.start()
for p in l:
p.join()
stop = time.time()
print('run time is %s' % (stop-start))
计算密集型任务多进程与多线程比较
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) # 耗时4.35s多
# p = Thread(target=work) # 耗时15.45s多
l.append(p)
p.start()
for p in l:
p.join()
stop = time.time()
print('run time is %s' % (stop-start))
死锁与递归锁
所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,如下就是死锁
from threading import Thread,Lock
import time
mutexA=Lock()
mutexB=Lock()
class MyThread(Thread):
def run(self):
self.func1()
self.func2()
def func1(self):
mutexA.acquire()
print('\033[41m%s 拿到A锁\033[0m' %self.name)
mutexB.acquire()
print('\033[42m%s 拿到B锁\033[0m' %self.name)
mutexB.release()
mutexA.release()
def func2(self):
mutexB.acquire()
print('\033[43m%s 拿到B锁\033[0m' %self.name)
time.sleep(2)
mutexA.acquire()
print('\033[44m%s 拿到A锁\033[0m' %self.name)
mutexA.release()
mutexB.release()
if __name__ == '__main__':
for i in range(10):
t=MyThread()
t.start()
解决方法,递归锁,在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。
这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁,二者的区别是:递归锁可以连续acquire多次,而互斥锁只能acquire一次
from threading import Thread,RLock
import time
mutexA=mutexB=RLock() #一个线程拿到锁,counter加1,该线程内又碰到加锁的情况,则counter继续加1,这期间所有其他线程都只能等待,等待该线程释放所有锁,即counter递减到0为止
class MyThread(Thread):
def run(self):
self.func1()
self.func2()
def func1(self):
mutexA.acquire()
print('\033[41m%s 拿到A锁\033[0m' %self.name)
mutexB.acquire()
print('\033[42m%s 拿到B锁\033[0m' %self.name)
mutexB.release()
mutexA.release()
def func2(self):
mutexB.acquire()
print('\033[43m%s 拿到B锁\033[0m' %self.name)
time.sleep(2)
mutexA.acquire()
print('\033[44m%s 拿到A锁\033[0m' %self.name)
mutexA.release()
mutexB.release()
if __name__ == '__main__':
for i in range(10):
t=MyThread()
t.start()
信号量
信号量也是一把锁,可以指定信号量为3,对比互斥锁同一时间只能有一个任务抢到锁去执行,信号量同一时间可以有3个任务拿到锁去执行,如果说互斥锁是合租房屋的人去抢一个厕所,那么信号量就相当于一群路人争抢公共厕所,公共厕所有多个坑位,这意味着同一时间可以有多个人上公共厕所,但公共厕所容纳的人数是一定的,这便是信号量的大小
from threading import Thread, Semaphore, current_thread
import time, random
sm = Semaphore(3) # 厕所有三个坑
def task():
with sm:
print('%s in 上厕所' % current_thread().getName())
time.sleep(random.randint(1, 3))
if __name__ == '__main__':
for i in range(10):
t = Thread(target=task)
t.start()
Event事件
线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其 他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就会变得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。 对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在 初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象, 而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行
1、事件被创建的时候,为FALSE状态 wait() 阻塞状态
wait(1) #状态为FALSE的时候,只等待1s
2、set() # 设置状态为True
3、clear() # 设置状态为FALSE
应用:链接数据库,检测数据库的可链接情况
from threading import Thread, Event, current_thread
import time
def conn():
count = 0 # 尝试三次链接
while not event.is_set(): # 数据库服务端没有开启
if count == 3:
print('%s您已经尝试太多次数了' % current_thread().getName())
return # 三次之后结束
print('%s正在尝试第%s次链接' % (current_thread().getName(), count))
event.wait(0.5) # 等0.5秒就往下执行
count += 1
print('%s链接上了' % current_thread().getName())
def check():
time.sleep(3) # 检测数据库服务端是否开启
event.set() # 开启之后发信号
if __name__ == '__main__':
event = Event()
co1 = Thread(target=conn)
co2 = Thread(target=conn)
ch = Thread(target=check)
co1.start()
co2.start()
ch.start()
定时器
定时器,指定n秒后执行某操作
from threading import Timer
def hello():
print("hello, world")
t = Timer(1, hello)
t.start() # after 1 seconds, "hello, world" will be printed
定时器是异步非阻塞的
from threading import Thread, Condition, Timer
import time
def func():
print('时间同步')
while True:
Timer(2, func).start() # 异步非阻塞
time.sleep(2)
应用:验证码定时刷新
from threading import Timer
import random
class Mycode:
def __init__(self):
self.timer(10) # 直接执行
self.cache = None # 验证码缓存
self.t = None # 定时生成新的验证码
@staticmethod
def auth_code(n=4): # 生成一个4为的验证码
res = ''
for i in range(n):
s1 = str(random.randint(1, 9))
s2 = chr(random.randint(65, 90))
s = random.choice([s1, s2])
res += s
return res
def timer(self, interval=6): # 间隔6秒换一个新的
self.cache = self.auth_code()
print(self.cache)
self.t = Timer(interval, self.timer) # 6秒后,自动再次执行这个函数
self.t.start()
def main(self):
while True:
code = input('请输入验证码:')
if code.upper() == self.cache:
print('验证码正确')
self.t.cancel()
break
if __name__ == '__main__':
obj = Mycode()
obj.main()