Python全栈【进程、线程、IO多路复用】 |
本节内容:
- 进程
- 线程
- 线程锁
- 协程
- 事件驱动
- I/O多路复用
- selectors
- socketserver
进程 |
1、进程就是一个程序在一个数据集上的一次动态执行过程,进程是资源分配的最小单元。
2、进程一般由程序、数据集、进程控制块三部分组成。
编写的程序用来描述进程要完成哪些功能以及如何完成;
数据集则是程序在执行过程中所需要使用的资源;
进程控制块用来记录进程的外部特征,描述进程的执行变化过程,系统可以利用它来控制和管理进程,它是系统感知进程存在的唯一标志。
3、线程的上一级就是进程,进程可包含很多线程,进程和线程的区别是进程间的数据不共享,多进程也可以用来处理多任务,不过多进程很消耗资源,
计算型的任务最好交给多进程来处理,IO密集型最好交给多线程来处理,此外进程的数量应该和cpu的核数保持一致。
线程 |
1、线程也叫轻量级进程,它是一个基本的CPU执行单元,也是程序执行过程中的最小单元,由线程ID、程序计数器、寄存器集合和堆栈共同组成。
2、线程的引入减小了程序并发执行时的开销,提高了操作系统的并发性能。
3、线程没有自己的系统资源。
4、多任务可以由多进程完成,也可以由一个进程内的多线程完成,一个进程内的所有线程,共享同一块内存python中创建线程比较简单,导入threading模块,下面来看一下代码中如何创建多线程。
创建一个新线程:
import time import threading def f1(i): time.sleep(1) print(i) if __name__ == '__main__': for i in range(5): t = threading.Thread(target=f1, args=(i,)) t.start() print('start') # 主线程等待子线程完成,子线程并发执行 # 每次数字顺序不一 # start # 0 # 1 # 2 # 4 # 3
主线程从上到下执行,创建5个子线程,打印出'start',然后等待子线程执行完结束,如果想让线程要一个个依次执行完,而不是并发操作,那么就要使用join方法。
import time import threading def f1(i): time.sleep(1) print(i) if __name__ == '__main__': li = [] for i in range(5): t = threading.Thread(target=f1, args=(i,)) t.start() li.append(t) for t in li: t.join() print('start') # 每次数字顺序不一 # 0 # 4 # 3 # 2 # 1 # start
上面的代码不适用join的话,主线程会默认等待子线程结束,才会结束,还有一种守护线程,即子线程守护主线程,主线程结束守护线程也结束。
import time import threading def f1(i): time.sleep(1) print(i) if __name__ == '__main__': for i in range(5): t = threading.Thread(target=f1, args=(i,)) t.setDaemon(True) #守护线程设置在start之前 t.start() print('start') # start
除此之外,自己还可以为线程自定义名字,通过 t = threading.Thread(target=f1, args=(i,), name='mythread{}'.format(i)) 中的name参数,除此之外,Thread还有一下一些方法
t.getName() : 获取线程的名称
t.setName() : 设置线程的名称
t.name : 获取或设置线程的名称
t.is_alive() : 判断线程是否为激活状态
t.isAlive() :判断线程是否为激活状态
t.isDaemon() : 判断是否为守护线程
线程锁 |
死锁示例:
import threading,time class MyThread(threading.Thread): def funcA(self): A.acquire() print(self.name,'got A',time.ctime()) time.sleep(2) B.acquire() print(self.name,'got B',time.ctime()) B.release() A.release() def funcB(self): B.acquire() print(self.name,'got A',time.ctime()) time.sleep(1) A.acquire() print(self.name,'got B',time.ctime()) A.release() B.release() def run(self): self.funcA() self.funcB() li = [] A = threading.Lock() B = threading.Lock() for i in range(5): t = MyThread() li.append(t) for j in li: j.start() for k in li: k.join() # Thread-1 got A # Thread-1 got B # Thread-1 got A # Thread-2 got A #线程阻塞,1与2都不先释放锁,就出现了死锁 # 在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源, # 就会造成死锁,因为系统判断这部分资源都正在使用,所有这两个线程在无外力作用下将一直等待下去。
解决方法:递归锁
import threading,time class MyThread(threading.Thread): def funcA(self): r_lock.acquire() print(self.name,'got A',time.ctime()) time.sleep(2) r_lock.acquire() print(self.name,'got B',time.ctime()) r_lock.release() r_lock.release() def funcB(self): r_lock.acquire() print(self.name,'got A',time.ctime()) time.sleep(1) r_lock.acquire() print(self.name,'got B',time.ctime()) r_lock.release() r_lock.release() def run(self): self.funcA() self.funcB() li = [] r_lock = threading.RLock() for i in range(5): t = MyThread() li.append(t) for j in li: j.start() for k in li: k.join()
Lock如果多次获取锁的时候会出错,而RLock允许在同一线程中被多次acquire,但是需要用n次的release才能真正释放所占用的琐,一个线程获取了锁在释放之前,其他线程只有等待。
为了支持在同一线程中多次请求同一资源,python提供了“可重入锁”:threading.RLock。RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次acquire。
线程间通讯Event |
Event是线程间通信最常见的机制之一,主要用于主线程控制其他线程的执行,主要用过wait,clear,set,这三个方法来实现的
红绿灯示例:
import time import threading def lighter(): count = 0 while 1: if count<30: if not event.is_set(): event.set() print('33[32;1m绿灯33[1m') elif count<34: print('33[33;1m黄灯33[1m') elif count<60: event.clear() print('33[31;1m红灯33[1m') else: count = 0 count+=1 time.sleep(0.2) def car(n): count =0 while 1: event.wait() print('汽车【%s】通过'%n) count+=1 time.sleep(1) event = threading.Event() l1 =threading.Thread(target=lighter) l1.start() c1 = threading.Thread(target=car,args=('奔驰',)) c1.start() # 绿灯 # 汽车【奔驰】通过 # 绿灯 # 绿灯 # 绿灯 # 绿灯 # 黄灯 # 汽车【奔驰】通过 # 黄灯 # 黄灯 # 黄灯 # 红灯 # 红灯 # 红灯
import threading,time class Boss(threading.Thread): def run(self): print("BOSS:今晚大家都要加班到22:00。") print(event.isSet()) event.set() time.sleep(5) print("BOSS:<22:00>可以下班了。") print(event.isSet()) event.set() class Worker(threading.Thread): def run(self): event.wait() print("Worker:哎……命苦啊!") time.sleep(1) event.clear() event.wait() print("Worker:OhYeah!") if __name__=="__main__": event=threading.Event() threads=[] for i in range(5): threads.append(Worker()) threads.append(Boss()) for t in threads: t.start() for t in threads: t.join()
信号量(semaphore) |
信号量用来控制线程并发数的,BoundedSemaphore或Semaphore管理一个内置的计数 器,每当调用acquire()时-1,调用release()时+1。
计数器不能小于0,当计数器为 0时,acquire()将阻塞线程至同步锁定状态,直到其他线程调用release()。(类似于停车位的概念)
BoundedSemaphore与Semaphore的唯一区别在于前者将在调用release()时检查计数 器的值是否超过了计数器的初始值,如果超过了将抛出一个异常。
import threading,time class myThread(threading.Thread): def run(self): if semaphore.acquire(): print(self.name) time.sleep(5) semaphore.release() if __name__=="__main__": semaphore=threading.Semaphore(5) thrs=[] for i in range(100): thrs.append(myThread()) for t in thrs: t.start()
队列 |
队列的方法:
q = queue.Queue(maxsize=0) # 构造一个先进显出队列,maxsize指定队列长度,为0时,表示队列长度无限制。 q.join() # 等到队列为kong的时候,在执行别的操作 q.qsize() # 返回队列的大小 (不可靠) q.empty() # 当队列为空的时候,返回True 否则返回False (不可靠) q.full() # 当队列满的时候,返回True,否则返回False (不可靠) q.put(item, block=True, timeout=None) # 将item放入Queue尾部,item必须存在,参数block默认为True,表示当队列满时,会等待 # 为False时为非阻塞,此时如果队列已满,会引发queue.Full 异常。 可选参数timeout,表示会阻塞设置的时间, # 如果在阻塞时间里 队列还是无法放入,则引发 queue.Full 异常 q.get(block=True, timeout=None) # 移除并返回队列头部的一个值,可选参数block默认为True,表示获取值的时候,如果队列为空,则阻塞 # 阻塞的话若此时队列为空,则引发queue.Empty异常。 可选参数timeout,表示会阻塞设置的时间, q.get_nowait() # 等效于 get(item,block=False)
队列的三种进出模式:
import queue q= queue.Queue(3) q.put(12) q.put('hello') q.put({'name':'alex'}) while 1: data = q.get() print(data) print('================') ###################################################### import queue q= queue.LifoQueue(3) q.put(12) q.put('hello') q.put({'name':'alex'}) q.qsize() while 1: data = q.get() print(data) print('================') ####################################################### import queue q= queue.PriorityQueue() q.put([2,12]) q.put([1,'hello']) q.put([3,{'name':'alex'}]) q.qsize() while 1: data = q.get() print(data[1]) print('================')
生产者消费者模型:
def producer(num):
for i in range(num):
q.put(i)
print('将{}添加到队列中'.format(i))
time.sleep(1)
def consumer(num):
count = 0
while count < num:
i = q.get()
print('将{}从队列取出'.format(i))
time.sleep(2)
count += 1
q = queue.Queue(10)
t1 = threading.Thread(target=producer, args=(10,))
t1.start()
t2 = threading.Thread(target=consumer, args=(10,))
t2.start()
进程与线程的区别 |
一个程序至少有一个进程,一个进程至少有一个线程.(进程可以理解成线程的容器)
进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。
但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈)但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.
一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.
协程 |
协程,又称微线程,协程执行看起来有点像多线程,但是事实上协程就是只有一个线程,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显,此外因为只有一个线程,不需要多线程的锁机制,也不存在同时写变量冲突。协程的适用场景:当程序中存在大量不需要CPU的操作时(IO)下面来看一个利用协程例子
import time import queue def consumer(name): print("--->ready to eat baozi...") while True: new_baozi = yield print("[%s] is eating baozi %s" % (name,new_baozi)) #time.sleep(1) def producer(): r = con.__next__() r = con2.__next__() n = 0 while 1: time.sleep(1) print("