5.1.27 网络并发编程总结
子进程与子线程对比:
# from multiprocessing import Process
# from multiprocessing import current_process
# from multiprocessing import Lock
# from multiprocessing import RLock
from multiprocessing import Semaphore
from multiprocessing import Event
# from multiprocessing import Queue
from threading import Thread
from threading import current_thread
from threading import active_count
from threading import enumerate
from threading import Lock
from threading import RLock
from threading import Semaphore
from concurrent.futures import ThreadPoolExecutor
from threading import Timer
import queue
进程是资源,线程是动作。
开一个进程,需要拷贝一份内存数据,所以开进程开销大。
进程之间的数据是隔离的,而线程之间的数据是共享的。
每个进程都默认有一个线程
daemon 守护进程与守护线程:
代码结束与进程结束的区别
守护进程: 主进程代码结束,守护进程就结束。 主进程代码执行完就结束,不管其子进程是否结束,守护进程立刻终止。
守护线程: 主线程进程结束,守护进程才结束。主进程代码执行结束,包括其子线程也结束,守护线程才终止。
相同点: 守护进程 | 守护线程 必须在start之前就进行设置 t.deamon = True
守护进程 | 守护线程 不能再开启子进程 | 子线程
互斥锁:并行的串行,牺牲效率来换取数据安全
各子进程的内存数据是相互独立的,所以需要传锁
p = Process(target=task, args=('进程%s' % i, multi_lock))
各子线程之间本来就共享资源,所以用的也是同一把锁,无需在线程之间进行传锁。
t = Thread(target=task)
GIL锁:
是解释器级别的锁,跟垃圾回收有关系。保证同一时刻同一进程只有一个线程运行。
python执行代码需要先获得GIL锁。但GIL锁保证不了代码数据。不同的代码要有不同的锁。
死锁:
是指2个或2个以上的进程或线程在执行过程中,因争夺资源造成的一种相互等待的现象。
解决办法:递归锁RLock
递归锁 RLock:
递归锁RLock内部维护着一个Lock和一个计数器counter, counter记录lock的 acquire的次数。每acquire一次就+1,每release一次就-1.
当某个线程获取RLock后,该线程可以多次acquire,但其他线程要acquire获取锁的时候,必须等待它锁的counter=0.
从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源
MetuxA = MetuxB =RLock()
信号量 :
from threading import Semaphore
信号量其实就是内部的一个计数器。用来记录可用资源的个数。同时最多可以有N个线程同时处理同一段代码。
初始化时指定可用资源的个数,当acquire一次,计数器就减1,release一次,计数器就加1.
当计数器=0,acquire()就必须等待,直到别的进程|线程调用release()
计算密集型用多进程,IO密集型用多线程。多进程有并行的优势,可以用到多核。多线程只能并发,不能并行。
队列: 效率 + 安全
生产者和消费都必须在同一台机器上,弊端
进程队列: 先进先出
from multiprocessing import Queue
线程队列:
import queue
# 先进先出: q = queue.Queue(3) # 后进先出: ql = queue.LifoQueue(3) #优先级队列,数字越小,优先越高 qp = queue.PriorityQueue(3) qp.put((10, '3')) # 10是优先级
僵尸进程: 子进程先于父进程结束,子进程未被父进程调用wait,子进程变成僵尸进程,父进程结束后会去清理僵尸进程,僵尸进程会占用进程数量,所以高并发下僵尸进程是有害的。
孤儿进程: 父进程先于子进程结束,子进程托管给init进程。孤儿进程是无害的。
Event事件: 线程之间的交互 一个线程可以等待,让另一个线程去唤醒。 线程之间的依赖关系。
event=Event()
event.isSet() # 返回event的状态值 True False
event.wait() #等待其他线程发出event.set()后才继续往下执行 event.isSet() == False 将阻塞线程
event.wait(3) #如果超过3秒,即使没有收到event.set()也会继续往下执行
event.set() # 设置event的状态值为True, 所有阻塞池的线程激活进入就绪状态,等待操作系统调度
event.clear() # 恢复event的状态值为False
模拟多个客户端连接服务器端,检测服务器是否正常运行? 服务器要先起来,客户端才可以连接。
定时器Timer:
多少秒后执行什么函数
def task(name): print(name) tm = Timer(5, task, args=('定时器',)) tm.start() # 运行结果: # 定时器 # 一个Timer只执行一次 # Process finished with exit code 0
每隔多少秒就执行一次动作,用到递归
# 每5秒输出一行 ’定时器‘
def task(name): print(name) tm = Timer(5, task, args=(name,)) tm.start() task('定时器')
多线程实现 网络通讯并发
异步调用与回调机制:
提交任务的两种方式
#1、同步调用: 提交完任务后,就在原地等待任务执行完毕,拿到结果,再执行下一行代码,导致程序是串行执行
pool = ThreadPoolExecutor(3)
# 同步调用,等待执行结果 # eggon = pool.submit(la, 'eggon').result(timeout=2) eggon = pool.submit(la, 'eggon').result() pool.submit(weight(eggon)) #la 和weight是两个函数
#2、异步调用:提交完任务后,不原地等待任务执行完毕
pool = ThreadPoolExecutor(3)
# 异步调用,不等待执行结果,继续执行下一行代码 # pool.submit(la, 'eggon').add_done_callback(weight) 意思是: # 执行函数la,执行结束后,把pool.submit(la, 'eggon')对象当作weight的参数,再执行weight函数。所以weight函数的参数只能是一个。
def weight(obj)
obj = obj.result() # 取la函数的真实返回值
线程池:
from concurrent.futures import ThreadPoolExecutor pool_size = 3 pool = ThreadPoolExecutor(pool_size) while True: pool.submit(函数,函数的参数)
协程:
协程:单线程下的并发。又称微线程。是用户态的轻量级线程,即协程是由用户程序自己控制调度的
要遇到IO才切。
比线程开销更小。
修改共享数据不用加锁
yield greenlet都无法做到遇到IO就切换
gevent 可以监控多个任务之间的IO,遇到IO切换到另一个任务。
IO模型:
阻塞IO:
blocking IO的特点就是在IO执行的两个阶段(等待数据和拷贝数据两个阶段)都被block了。
非阻塞IO:
在非阻塞式IO中,用户进程其实是需要不断的主动询问kernel数据准备好了没有。如果没好,就会BlockingIOError的抛出异常。
具体做法:在可能发生IO阻塞的代码前,设置 server.setblocking(False),该语句后面的代码遇到阻塞,就会抛出异常。
多路复用:
它的基本原理就是select/epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程