WEEK10:Python协程、异步IO
- 使用场景:IO操作不占用CPU,计算占用CPU。Python的多线程不适合CPU密集操作型的任务,适合IO操作密集型的任务。
- 多进程的基本语法
- 启动进程和在进程中启动线程
1 import multiprocessing 2 import time,threading 3 4 def thread_run(): #线程函数 5 print(threading.get_ident()) 6 7 def run(name): #进程函数 8 time.sleep(2) 9 print("hello",name) 10 t=threading.Thread(target=thread_run,) #在进程中启动线程 11 t.start() 12 13 if __name__ == "__main__": 14 for i in range(10): 15 p=multiprocessing.Process(target=run,args=('bob %s' %i,)) #启动进程 16 p.start()
- 获取父进程和子进程的ID
1 from multiprocessing import Process 2 import os 3 4 def info(title): 5 print(title) 6 print('module name:', __name__) #获取模块名称即父进程名称 7 print('parent process:', os.getppid()) #获取父进程ID 8 print('process id:', os.getpid()) #获取本进程的ID 9 print("\n\n") 10 11 def f(name): 12 info('called from child process function f') 13 print('hello', name) 14 15 if __name__ == '__main__': 16 info('main process line') #父进程为主函数'__main__', 17 p = Process(target=f, args=('bob',)) #父进程为info函数 18 p.start()
- 进程间通信
- Queue #发数据使用put(),收数据使用get()【数据传递】
1 from multiprocessing import Process, Queue #进程间通信使用的是multiprocessing中的Queue 2 import threading 3 4 def f(qq): 5 print("in child:",qq.qsize()) 6 qq.put([42, None, 'hello']) #发数据 7 8 if __name__ == '__main__': 9 q = Queue() 10 q.put("test123") 11 p = Process(target=f, args=(q,)) 12 p.start() 13 p.join() 14 print("444",q.get_nowait()) #收数据 15 print("444",q.get_nowait()) #收数据
- Pipes #发数据使用send(),收数据使用recv()【数据传递】
1 from multiprocessing import Process, Pipe 2 3 def f(conn): 4 conn.send([42, None, 'hello from child']) 5 conn.send([42, None, 'hello from child2']) 6 print("from parent:",conn.recv()) # prints "from parent:张洋可好" 7 conn.close() 8 9 if __name__ == '__main__': 10 parent_conn, child_conn = Pipe() #生成管道实例 11 p = Process(target=f, args=(child_conn,)) 12 p.start() 13 print(parent_conn.recv()) # prints "[42, None, 'hello from child']" 14 print(parent_conn.recv()) # prints "[42, None, 'hello from child2']" 15 parent_conn.send("张洋可好") 16 p.join()
- Managers #【数据共享】
1 from multiprocessing import Process, Manager 2 import os 3 def f(d, l): 4 d[os.getpid()] =os.getpid() #在字典中添加进程ID 5 l.append(os.getpid()) #在列表中添加进程ID 6 7 if __name__ == '__main__': 8 with Manager() as manager: 9 d = manager.dict() #生成一个字典,可在多个进程间共享和传递,不能使用d={} 10 l = manager.list(range(5))#生成一个列表,可在多个进程间共享和传递 11 p_list = [] 12 for i in range(10): 13 p = Process(target=f, args=(d, l)) 14 p.start() 15 p_list.append(p) 16 for res in p_list: #等待结果 17 res.join() 18 print(d) #打印字典 19 print(l) #打印列表
- Queue #发数据使用put(),收数据使用get()【数据传递】
- 进程锁Lock(进程同步) #防止程序输出是发生一个进程数据还没输出完,另外一个进程抢先输出的现象
1 from multiprocessing import Process, Lock 2 3 def f(l, i): 4 l.acquire() 5 print('hello world', i) 6 l.release() 7 8 if __name__ == '__main__': 9 lock = Lock() 10 for num in range(100): 11 Process(target=f, args=(lock, num)).start()
- 进程池
进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进程,那么程序就会等待,直到进程池中有可用的进程为止。
- 方法
- apply
- apply_sync
- 实例
1 from multiprocessing import Pool 2 import time,os 3 def Foo(i): 4 time.sleep(2) 5 print("in process",os.getpid()) 6 return i + 100 7 8 def Bar(arg): 9 print('-->exec done:', arg,os.getpid()) 10 11 if __name__ == '__main__': #手动执行时执行这句下面的指令,但是使用其他脚本调用时,下面的指令不执行 12 pool = Pool(5) #允许进程池同时放入5个进程,Pool(processes=5) 13 print("主进程",os.getpid()) 14 for i in range(10): 15 pool.apply_async(func=Foo, args=(i,), callback=Bar) #callback=回调(由主进程负责调用),执行完Foo之后再去执行Bar 16 #pool.apply(func=Foo, args=(i,)) #串行 17 #pool.apply_async(func=Foo, args=(i,)) #异步,并行 18 print('end') 19 pool.close() #先close在join,和进程、线程的先join再close方法相反 20 pool.join() #进程池中进程执行完毕后再关闭,如果不join,那么程序直接关闭。
- 方法
- 启动进程和在进程中启动线程
- 协程(Coroutine)
又称微线程,是用一种用户态的轻量级线程。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此,协程能保留上一次调用时的状态(即所有局部状态的一个特点组合),每次过程重入时,就相当于进入上一次调用的状态(进入上一次离开时所处的逻辑流的位置)
- 优点:
- 无需线程上下文切换的开销
- 无需原子操作锁定及同步的开销
- 方便切换控制流,简化编程模型
- 高并发+高扩展性+低成本:一个CPU可以支持上万个协程,所以很适合高并发处理
- 缺点
- 无法利用多核资源:协程的本质是个单线程,不能同时将单个CPU的多个核同时使用,协程需要和进程配合才能运行在多核上
- 进行阻塞操作时会阻塞掉整个程序
- 标准定义(条件)
- 必须在只有一个单线程里实现并发
- 修改共享数据不需加锁
- 用户程序里自己保存多个控制流的上下文
- 一个协程遇到IO操作自动切换到其他协程
- 实现
- 使用生成器yield实现
(详细代码见生成器那一章节) - Greenlet (手动切换)
greenlet是一个用C实现的协程模块,相比与python自带的yield,它可以使你在任意函数之间随意切换,而不需把这个函数先声明为生成器
1 from greenlet import greenlet 2 def test1(): 3 print(12) 4 gr2.switch() #切换到gr2 5 print(34) 6 gr2.switch() 7 def test2(): 8 print(56) 9 gr1.switch() 10 print(78) 11 12 gr1 = greenlet(test1) #启动一个携程 13 gr2 = greenlet(test2) 14 gr1.switch() #切换到gr1(test1)开始执行 15 #先打印12;test1中切换到gr2,打印56;test2中切换到gr1,打印34;test1中再次切换到gr2,打印78
- Gevent (自动切换)
gevent是一个第三方库,可以轻松通过gevent实现并发或异步编程,在gevent中用到的主要模式是Greenlet,它是以C扩展模块的形式接入python的轻量级协程。greenlet全部运行在主程序操作系统进程的内部,但他们被协作式的调度。
1 import gevent 2 def foo(): 3 print('Running in foo') 4 gevent.sleep(2) 5 print('Explicit context switch to foo again') 6 def bar(): 7 print('Explicit精确的 context内容 to bar') 8 gevent.sleep(1) 9 print('Implicit context switch back to bar') 10 def func3(): 11 print("running func3 ") 12 gevent.sleep(0) 13 print("running func3 again ") 14 15 gevent.joinall([ 16 gevent.spawn(foo), #以列表的形式生成协程实例 17 gevent.spawn(bar), 18 gevent.spawn(func3), 19 ]) 20 21 # 输出结果: 22 # Running in foo 23 # Explicit精确的 context内容 to bar 24 # running func3 25 # running func3 again 26 # Implicit context switch back to bar 27 # Explicit context switch to foo again
- 简易爬虫和协程的结合
1 import gevent,time 2 from urllib.request import urlopen 3 from gevent import monkey 4 #不需使用monkey.patch_all(),否则使用的协程gevent检测不到urllib中的IO操作 5 monkey.patch_all() #把当前程序的所有的IO操作给我单独的做上标记 6 7 def f(url): 8 print('GET: %s' % url) 9 resp = urlopen(url) 10 data = resp.read() 11 print('%d bytes received from %s.' % (len(data), url)) 12 13 #并行 14 async_time_start = time.time() 15 gevent.joinall([ 16 gevent.spawn(f, 'https://www.python.org/'), 17 gevent.spawn(f, 'https://www.yahoo.com/'), 18 gevent.spawn(f, 'https://github.com/'), 19 ]) 20 print("异步cost",time.time()-async_time_start ) 21 22 #串行 23 time_start = time.time() 24 urls = [ 'https://www.python.org/', 25 'https://www.yahoo.com/', 26 'https://github.com/' 27 ] 28 for url in urls: 29 f(url) 30 print("同步cost",time.time() - time_start)
- socket和协程的结合
- 服务端
1 import gevent 2 from gevent import socket, monkey 3 monkey.patch_all() 4 5 def server(port): 6 s = socket.socket() 7 s.bind(('0.0.0.0', port)) 8 s.listen(500) 9 while True: 10 cli, addr = s.accept() 11 gevent.spawn(handle_request, cli) 12 13 def handle_request(conn): 14 try: 15 while True: 16 data = conn.recv(1024) 17 print("recv:", data) 18 conn.send(data) 19 if not data: 20 conn.shutdown(socket.SHUT_WR) 21 except Exception as ex: 22 print(ex) 23 finally: 24 conn.close() 25 26 if __name__ == '__main__': 27 server(8001)
- 客户端
1 import socket 2 HOST = 'localhost' 3 PORT = 8001 4 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 5 s.connect((HOST, PORT)) 6 while True: 7 msg = bytes(input(">>:"), encoding="utf8") 8 s.sendall(msg) 9 data = s.recv(1024) 10 print('Received', data) 11 s.close()
- 服务端
- 使用生成器yield实现
- 优点:
- 事件驱动与异步IO
- 概念说明
- 用户空间和内核空间
现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方)。操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操心系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。针对linux操作系统而言,将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为内核空间,而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为用户空间。 - 进程切换
为了控制进程的执行,内核必须有能力挂起正在CPU上运行的进程,并恢复以前挂起的某个进程的执行。这种行为被称为进程切换。因此可以说,任何进程都是在操作系统内核的支持下运行的,是与内核紧密相关的。从一个进程的运行转到另一个进程上运行,这个过程中经过下面这些变化:1. 保存处理机上下文,包括程序计数器和其他寄存器;2. 更新PCB信息;3. 把进程的PCB移入相应的队列,如就绪、在某事件阻塞等队列;4. 选择另一个进程执行,并更新其PCB;5. 更新内存管理的数据结构;6. 恢复处理机上下文。总而言之就是很耗资源。 - 进程的阻塞
正在执行的进程,由于期待的某些事件未发生,如请求系统资源失败、等待某种操作的完成、新数据尚未到达或无新工作做等,则由系统自动执行阻塞原语(Block),使自己由运行状态变为阻塞状态。可见,进程的阻塞是进程自身的一种主动行为,也因此只有处于运行态的进程(获得CPU),才可能将其转为阻塞状态。当进程进入阻塞状态,是不占用CPU资源的。 - 文件描述符fd
文件描述符是计算机科学中的一个术语,是一个用于表述指向文件的引用的抽象化概念。文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。 - 缓存 I/O
缓存 I/O 又被称作标准 I/O,大多数文件系统的默认 I/O 操作都是缓存 I/O。在 Linux 的缓存 I/O 机制中,操作系统会将 I/O的数据缓存在文件系统的页缓存( page cache)中,也就是说,数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。缓存 I/O 的缺点:数据在传输过程中需要在应用程序地址空间和内核进行多次数据拷贝操作,这些数据拷贝操作所带来的 CPU 以及内存开销是非常大的。 - 水平触发(LT)
默认工作模式,即当epoll_wait检测到某描述符事件就绪并通知应用程序时,应用程序可以不立即处理该事件;下次调用epoll_wait时,会再次通知此事件 - 边缘触发(ET)
当epoll_wait检测到某描述符事件就绪并通知应用程序时,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次通知此事件。(直到你做了某些操作导致该描述符变成未就绪状态了,也就是说边缘触发只在状态由未就绪变为就绪时只通知一次)
- 用户空间和内核空间
- IO模式
对于一次IO访问(以read举例),数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。所以说,当一个read操作发生时,它会经历两个阶段:1. 等待数据准备 ,2. 将数据从内核拷贝到进程中 。正式因为这两个阶段,linux系统产生了下面五种网络模式的方案:
- 信号驱动 I/O( signal driven IO)(不常用)
- 阻塞 I/O(blocking IO)
blocking IO的特点就是在IO执行的两个阶段都被block了 - 非阻塞 I/O(nonblocking IO)
nonblocking IO的特点是用户进程需要不断的主动询问kernel数据好了没有 - I/O 多路复用( IO multiplexing)
IO multiplexing就是我们说的select,poll,epoll,有些地方也称这种IO方式为event driven IO。select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。它的基本原理就是select,poll,epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。
I/O 多路复用的特点是通过一种机制一个进程能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,select()函数就可以返回 - 异步 I/O(asynchronous IO)
用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了 - 各种IO模式的区别
- Python Select解析(I/O 多路复用)
- select、poll、epoll之间的区别
- 说明
epoll是Linux目前大规模网络并发程序开发的首选模型。在绝大多数情况下性能远超select和poll。目前流行的高性能web服务器Nginx正式依赖于epoll提供的高效网络套接字轮询服务。但是,在并发连接不高的情况下,多线程+阻塞I/O方式可能性能更好 - 发展历史简介
select出现是1984年在BSD里面实现的。14年之后也就是1997年才实现了poll,其实拖那么久也不是效率问题,而是那个时代的硬件实在太弱,一台服务器处理1千多个链接简直就是神一样的存在了,select很长段时间已经满足需求。2002, 大神 Davide Libenzi 实现了epoll。 - 代码实现
- 服务端
1 import select,socket,queue 2 #创建服务端实例 3 server = socket.socket() 4 server.bind(('localhost',9000)) 5 server.listen(1000) 6 #设置为非阻塞模式 7 server.setblocking(False) 8 #初始化一个队列,后面存要返回给客户端的数据 9 msg_dic={} 10 #存储链接的列表 11 inputs = [server,] #inputs = [server,conn,conn2] #[conn2,] 12 outputs = [] #outputs = [r1,] 13 14 while inputs: 15 #(inputs, outputs, inputs) 16 #内核需要监测的链接,需要返回的连接队列(需要server端给发数据的客户端),返回异常的链接(需要将需要监控的链接传给它,所以和input是一样的) 17 readable ,writeable,exceptional= select.select(inputs, outputs, inputs ) 18 # print(readable,writeable,exceptional) 19 for r in readable: 20 if r is server: #如果readable中返回的是server,则代表是新链接 21 conn,addr = server.accept() 22 print("来了个新连接:",addr) 23 #因为这个新建立的连接还没发数据过来,现在就接收的话程序就报错了, 24 #所以要想实现客户端发数据来时server端能知道,就需要让select再监测这个conn 25 inputs.append(conn) 26 #给新链接初始化一个队列,用来存服务端给客户端传送的数据 27 msg_dic[conn]=queue.Queue() 28 #将这个链接设置为非阻塞模式 29 conn.setblocking(False) 30 else: #否则就是旧的链接,直接收数据即可 31 data = r.recv(1024) 32 if data: #接收到数据,说明链接是正常的 33 print("收到来自",r.getpeername(),"的数据:",data) 34 if r not in outputs: 35 outputs.append(r) # 将链接放入需要返回数据的链接队列里 36 msg_dic[r].put(data.upper()) #将接收到的数据变为大写后存在字典中 37 else: #否则链接是断开的 38 print("链接关闭:",addr) 39 #删除断开的链接的所有数据 40 if r in outputs: 41 outputs.remove(r) 42 inputs.remove(r) 43 r.close() 44 del msg_dic[r] 45 46 #服务端接收到客户端的数据之后下一次循环的时候再发送给客户端 47 for w in writeable: #要返回给客户端的连接列表 48 try: 49 data_to_client = msg_dic[w].get_nowait() #将字典中要发送的数据取出 50 except queue.Empty: 51 print("此链接无数据要返回:",w) 52 outputs.remove(w) 53 else: 54 w.send(data_to_client) #返回给客户端源数据 55 #确保下次循环的时候writeable,不返回这个已经处理完的连接了 56 outputs.remove(w) 57 58 #删除错误链接的所有内容 59 for e in exceptional: 60 print("此链接出错:",e) 61 inputs.remove(e) 62 if e in outputs: 63 outputs.remove(e) 64 e.close() 65 del msg_dic[e]
-
客户端
1 import socket 2 HOST = 'localhost' 3 PORT = 9000 4 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 5 s.connect((HOST, PORT)) 6 while True: 7 msg = bytes(input(">>:"), encoding="utf8") 8 s.sendall(msg) 9 data = s.recv(1024) 10 print('Received', data) 11 s.close()
- 服务端
- selectors模块
它的功能与linux的epoll,还是select模块,poll等类似;实现高效的I/O multiplexing, 常用于非阻塞的socket的编程中。- 模块中定义的类
- 模块定义了一个 BaseSelector的抽象基类, 以及它的子类,包括:SelectSelector, PollSelector, EpollSelector, DevpollSelector, KqueueSelector。
- 另外还有一个DefaultSelector类,它其实是以上其中一个子类的别名而已,它自动选择为当前环境中最有效的Selector,所以平时用 DefaultSelector类就可以了,其它用不着。
- 模块中定义的两个常量,用于描述event mask
- EVENT_READ :表示可读的, 它的值其实是1
- EVENT_WRITE:表示可写的; 它的值其实是2
- 模块定义了一个 SelectorKey类, 一般用这个类的实例 来描述一个已经注册的文件对象的状态, 这个类的几个属性常用到
- fileobj:表示已经注册的文件对象
- fd:表示文件对象的描述符,是一个整数,它是文件对象的 fileno()方法的返回值
- events:表示注册一个文件对象时,我们等待的events, 即上面的event mask是可读还是可写
- data: 表示注册一个文件对象是邦定的data
- 抽象基类中的方法
- register(fileobj, events, data=None)
- 作用:注册一个文件对象
- 参数
- fileobj——即可以是fd 也可以是一个拥有fileno()方法的对象
- events——上面的event mask 常量
- 返回值: 一个SelectorKey类的实例
- unregister(fileobj)
- 作用: 注销一个已经注册过的文件对象
- 返回值:一个SelectorKey类的实例
- modify(fileobj, events, data=None)
- 作用:用于修改一个注册过的文件对象,比如从监听可读变为监听可写;它其实就是register() 后再跟unregister(),但是使用modify( ) 更高效
- 返回值:一个SelectorKey类的实例
- select(timeout=None)
- 作用: 用于选择满足我们监听的event的文件对象
- 返回值: 是一个(key, events)的元组, 其中key是一个SelectorKey类的实例, 而events 就是 event Mask(EVENT_READ或EVENT_WRITE,或者二者的组合)
- close()
- 作用:关闭 selector。 最后一定要记得调用它, 要确保所有的资源被释放\
- get_key(fileobj)
- 作用: 返回注册文件对象的 key
- 返回值 :一个SelectorKey类的实例
- register(fileobj, events, data=None)
- 修改linux系统同时打开的最大文件数 ulimit (ulimit为shell内建指令,可用来控制shell执行程序的资源)
一般缺省值是1024,对一台繁忙的服务器来说,这个值偏小
- 参数:
- -a:显示目前资源限制的设定。
- -c <core文件上限>:设定core文件的最大值,单位为区块。
- -d <数据节区大小>:程序数据节区的最大值,单位为KB。
- -f <文件大小>:shell所能建立的最大文件,单位为区块。
- -H:设定资源的硬性限制,也就是管理员所设下的限制。
- -m <内存大小>:指定可使用内存的上限,单位为KB。
- -n <文件数目>:指定同一时间最多可打开的文件数。
- -p <缓冲区大小>:指定管道缓冲区的大小,单位512字节。
- -s <堆栈大小>:指定堆叠的上限,单位为KB。
- -S:设定资源的弹性限制。
- -t <CPU时间>:指定CPU使用时间的上限,单位为秒。
- -u <进程数目>:用户最多可启动的进程数目。
- -v <虚拟内存大小>:指定可使用的虚拟内存上限,单位为KB。
- 系统调优
- 暂时地,适用于通过 ulimit 命令登录 shell 会话期间。
- ulimit -a用来显示当前的各种用户进程限制。Linux对于每个用户,系统限制其最大进程数。为提高性能,可以根据设备资源情况,设置各linux用户的最大进程数,某linux用户的最大进程数设为10000个:ulimit -u 10000
- 对于需要做许多 socket 连接并使它们处于打开状态的 Java 应用程序而言,最好通过使用 ulimit -n xx 修改每个进程可打开的文件数,缺省值是 1024。ulimit -n 4096 将每个进程可以打开的文件数目加大到4096,缺省为1024其他建议设置成无限制(unlimited)的一些重要设置是:
- 数据段长度:ulimit -d unlimited
- 最大内存大小:ulimit -m unlimited
- 堆栈大小:ulimit -s unlimited
- CPU 时间:ulimit -t unlimited
- 虚拟内存:ulimit -v unlimited
- 永久地,通过将一个相应的 ulimit 语句添加到由登录 shell 读取的文件中, 即特定于 shell 的用户资源文件
- 解除 Linux 系统的最大进程数和最大文件打开数限制
vi /etc/security/limits.conf
# 添加如下的行
* soft noproc 11000
* hard noproc 11000
* soft nofile 4100
* hard nofile 4100说明:* 代表针对所有用户,noproc 是代表最大进程数,nofile 是代表最大文件打开数
- 让 SSH 接受 Login 程式的登入,方便在 ssh 客户端查看 ulimit -a 资源限制
- vi /etc/ssh/sshd_config,把 UserLogin 的值改为 yes,并把 # 注释去掉
- 重启 sshd 服务,/etc/init.d/sshd restart
- 修改所有 linux 用户的环境变量文件
vi /etc/profile
ulimit -u 10000
ulimit -n 4096
ulimit -d unlimited
ulimit -m unlimited
ulimit -s unlimited
ulimit -t unlimited
ulimit -v unlimited
- 解除 Linux 系统的最大进程数和最大文件打开数限制
- 在程序里面需要打开多个文件,进行分析,系统一般默认数量是1024,(用ulimit -a可以看到)对于正常使用是够了,但是对于程序来讲,就太少了,修改2个文件
- /etc/security/limits.conf
vi /etc/security/limits.conf
加上:
* soft nofile 8192
* hard nofile 20480 - /etc/pam.d/login
session required /lib/security/pam_limits.so
#另外确保/etc/pam.d/system-auth文件有下面内容
#session required /lib/security/$ISA/pam_limits.so
#这一行确保系统会执行这个限制 - 一般用户的.bash_profile
ulimit -n 1024,重新登陆ok
- /etc/security/limits.conf
- 暂时地,适用于通过 ulimit 命令登录 shell 会话期间。
- /proc目录
- /proc目录里面包括很多系统当前状态的参数,/proc/sys/fs/file-max和/proc/sys/fs/inode-max是对整个系统的限制,并不是针对用户的
- /proc目录中的值可以进行动态的设置,若希望永久生效,可以修改/etc/sysctl.conf文件,并使用下面的命令确认:sysctl -p
- 参数:
- 实例
- 服务端
1 import selectors 2 import socket 3 4 sel = selectors.DefaultSelector() #生成selectors对象 5 6 def accept(sock, mask): 7 conn, addr = sock.accept() # Should be ready 8 print('accepted', conn, 'from', addr,mask) 9 conn.setblocking(False) #将链接设置为非阻塞模式 10 sel.register(conn, selectors.EVENT_READ, read) #新连接注册read回调函数 11 12 def read(conn, mask): 13 data = conn.recv(1024) # Should be ready 14 if data: 15 print('echoing', repr(data), 'to', conn) 16 conn.send(data) # Hope it won't block 17 else: 18 print('closing', conn) 19 sel.unregister(conn) 20 conn.close() 21 22 sock = socket.socket() 23 sock.bind(('localhost', 9998)) 24 sock.listen(100) 25 sock.setblocking(False) 26 sel.register(sock, selectors.EVENT_READ, accept) 27 28 while True: 29 events = sel.select() #默认阻塞,有活动连接就返回活动的连接列表 30 for key, mask in events: 31 callback = key.data #调用accept 32 callback(key.fileobj, mask) #key.fileobj就是那个socket链接
- 客户端
1 import socket 2 messages = [ b'This is the message. ', 3 b'It will be sent ', 4 b'in parts.', 5 ] 6 server_address = ('localhost', 9998) 7 8 # Create a TCP/IP socket 9 socks = [ socket.socket(socket.AF_INET, socket.SOCK_STREAM) for i in range(100)] 10 print(socks) 11 # Connect the socket to the port where the server is listening 12 print('connecting to %s port %s' % server_address) 13 for s in socks: 14 s.connect(server_address) 15 16 for message in messages: 17 # Send messages on both sockets 18 for s in socks: 19 print('%s: sending "%s"' % (s.getsockname(), message) ) 20 s.send(message) 21 # Read responses on both sockets 22 for s in socks: 23 data = s.recv(1024) 24 print( '%s: received "%s"' % (s.getsockname(), data) ) 25 if not data: 26 print( 'closing socket', s.getsockname() )
- 服务端
- 模块中定义的类
- select、poll、epoll之间的区别
- 概念说明
仰天大笑出门去,吾辈岂是蓬蒿人!