1、local 在多个线程之间使用threading.local对象,能够实现多个线程之间的数据隔离 import time import random from threading import Thread,local loc = local() def func1(): global loc print(loc.name,loc.age) def func2(name,age): global loc loc.name = name loc.age = age time.sleep(random.uniform(0,2)) func1() Thread(target=func2,args=('明哥',19)).start() Thread(target=func2,args=('小弟',17)).start() 结果: 小弟 17 明哥 19 解释: 在平时的线程中,应该每次打印的结果应该都是最后进来的线程的数据,即第一次的数据会被第二次的数据覆盖, 可是使用了local对象,能够实现线程的数据隔离,所以打印的两次结果都不同。 原理: local对象会建立一个大字典,字典的键是线程的id,值也是一个字典,存储数据,例如: { 线程id1:{'name':'明哥',age:19} 线程id2:{'name':'小弟',age:17} ... } 2、 1、阻塞IO(blocking IO)当用户进程调用了recv/recvfrom这些系统调用时,操做系统就开始了IO的第一个阶段:准备数据。由于不少时候数据一开始尚未到达(好比,尚未收到一个完整的UDP包),这个时候操做系统就要等待足够的数据到来。 而在用户进程这边,整个进程会被阻塞。操做系统一直等到数据准备好了,它就会将数据拷贝到用户内存,而后返回结果,用户进程才解除block的状态,从新运行起来。 因此,blocking IO的特色就是在IO执行的两个阶段(等待数据和拷贝数据两个阶段)都被block了。 几乎全部的程序员第一次接触到的网络编程都是从listen()、send()、recv() 等接口开始的,使用这些接口能够很方便的构建服务器/客户机的模型。然而大部分的socket接口都是阻塞型的。 阻塞型接口是指系统调用(通常是IO接口)不返回调用结果并让当前线程一直阻塞,只有当该系统调用得到结果或者超时出错时才返回。 实际上,除非特别指定,几乎全部的IO接口 ( 包括socket接口 ) 都是阻塞型的。这给网络编程带来了一个很大的问题,如在调用recv(1024)的同时,线程将被阻塞,在此期间,线程将没法执行任何运算或响应任何的网络请求。 解决方案: 在服务器端使用多线程(或多进程)。多线程(或多进程)的目的是让每一个链接都拥有独立的线程(或进程),这样任何一个链接的阻塞都不会影响其余的链接。 该方案的问题是: 开启多进程或都线程的方式,在遇到要同时响应成百上千路的链接请求,则不管多线程仍是多进程都会严重占据系统资源,下降系统对外界响应效率,并且线程与进程自己也更容易进入假死状态。 改进方案: 不少程序员可能会考虑使用“线程池”或“链接池”。“线程池”旨在减小建立和销毁线程的频率,其维持必定合理数量的线程,并让空闲的线程从新承担新的执行任务。“链接池”维持链接的缓存池,尽可能重用已有的链接、减小建立和关闭链接的频率。这两种技术均可以很好的下降系统开销,都被普遍应用不少大型系统,如websphere、tomcat和各类数据库等。 改进后方案其实也存在着问题: “线程池”和“链接池”技术也只是在必定程度上缓解了频繁调用IO接口带来的资源占用。并且,所谓“池”始终有其上限,当请求大大超过上限时,“池”构成的系统对外界的响应并不比没有池的时候效果好多少。因此使用“池”必须考虑其面临的响应规模,并根据响应规模调整“池”的大小。 对应上例中的所面临的可能同时出现的上千甚至上万次的客户端请求,“线程池”或“链接池”或许能够缓解部分压力,可是不能解决全部问题。总之,多线程模型能够方便高效的解决小规模的服务请求,但面对大规模的服务请求,多线程模型也会遇到瓶颈,能够用非阻塞接口来尝试解决这个问题。 2、非阻塞IO
非阻塞IO操做一些时,若是操做系统中的数据尚未准备好,它也不会出现阻塞,而是马上返回一个error。用户进程能够判断这个返回值是不是error,若是是它就知道数据尚未准备好,因而用户就能够在下次询问前的时间内作其余事情。若是数据准备好了,且用户再次进行询问时,那么数据就能够拷贝到用户内存(这一阶段仍然是阻塞的),而后返回。 其实在非阻塞式IO中,用户进程实际上是须要不断的主动询问操做系统数据是否准备好了。 例如: Server端: import socket sk = socket.socket() sk.bind(('127.0.0.1',8080)) sk.setblocking(False) # 设置当前的server为一个非阻塞IO模型 sk.listen() conn_lst = [] del_lst = [] while True: try: conn,addr = sk.accept() conn_lst.append(conn) except BlockingIOError: for conn in conn_lst: try: conn.send(b'hello') print(conn.recv(1024)) except (NameError,BlockingIOError):pass except ConnectionResetError: conn.close() del_lst.append(conn) for del_conn in del_lst: conn_lst.remove(del_conn) del_lst.clear() Client端: import socket sk = socket.socket() sk.connect(('127.0.0.1',8080)) while 1: print(sk.recv(1024)) sk.send(b'heiheihei') 这样就能够实现非阻塞IO下基于TCP的并发socket, 可是这样的非阻塞IO大量的占用了CPU致使了资源的浪费而且给CPU形成了很大的负担 3、多路复用IO
当用户进程调用了select,那么整个进程会被block,同时,操做系统会“监视”全部select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操做,将数据从操做系统拷贝到用户进程。 多路复用IO与阻塞IO: 多路复用IO须要使用两次系统调用,而阻塞IO只调用了一个系统调用。可是,用select的优点在于它能够同时处理多个链接,而阻塞IO只能处理一个。 例子: Server端: import select # 用来操做操做系统中的select(IO多路复用)机制 import socket sk = socket.socket() sk.bind(('127.0.0.1',8888)) sk.setblocking(False) sk.listen() r_lst = [sk,] while True: # 监视r_lst这个列表(全部待接收请求的对象都在这里),谁的请求来了就通知谁,而后把有请求的对象返回给r_l列表,也有可能两个请求一块儿来 r_l,_,_ = select.select(r_lst,[],[]) for item in r_l: # 循环有请求的对象,判断是什么类型,进行对应的操做 if item is sk: conn,addr = sk.accept() r_lst.append(conn) else: try: print(item.recv(1024)) item.send(b'hello') except ConnectionResetError: item.close() r_lst.remove(item) Client端: import socket sk = socket.socket() sk.connect(('127.0.0.1',8888)) while 1: sk.send(b'world') print(sk.recv(1024)) 多路复用IO小结: io多路复用(asycio tornado twisted)机制 select 在windows\mac\linux可用 底层是操做系统的轮询 有监听对象个数的限制 随着监听对象的个数增长,效率下降 poll 只在mac\linux可用 底层是操做系统的轮询 有监听对象个数的限制,可是比select能监听的个数多 随着监听对象的个数增长,效率下降 epoll 只在mac\linux可用 给每个要监听的对象都绑定了一个回调函数 再也不受到个数增长 效率下降的影响 socketserver机制 IO多路复用 + threading线程 selectors模块 帮助你在不一样的操做系统上进行IO多路复用机制的自动筛选 四、异步IO(Asynchronous I/O)
用户进程发起请求以后,操做系统会马上返回,全部不会造成阻塞,而后用户进程马上就能够开始去作其它的事。同时操做系统会等待数据准备完成,而后将数据拷贝到用户内存,当这一切都完成以后,会给用户进程发送一个signal,告诉它操做完成了。