Heim >Backend-Entwicklung >Python-Tutorial >Detaillierte Beschreibung der Analyse der Python-Select-Epoll-Umfrage
Der Unterschied zwischen select, poll und epoll
select
select erschien erstmals 1983 in 4.2BSD. Es überwacht mehrere Dateibeschreibungen über einen select()-Systemaufruf array von Symbolen (alles in Linux ist eine Datei, ein Blockgerät, eine Socket-Verbindung usw.). Wenn select() zurückkehrt, werden die bereiten Dateideskriptoren im Array geändert. Das Flag-Bit wird vom Kernel geändert (wird bereit), damit der Prozess diese Dateideskriptoren für nachfolgende Lese- und Schreibvorgänge erhalten kann (Auswahl überwacht kontinuierlich, wie viele Dateideskriptoren sich in einem Verzeichnis des Netzwerks geändert haben Schnittstelle Bereit werden Status [In der Netzwerkschnittstelle wird durch eine Verbindung eine „Datei“ erstellt. Wählen Sie nach Erreichen des Bereitschaftsstatus die Option „Dieser Dateideskriptor kann ausgeführt werden“) aus.
[Socketserver verarbeitet mehrere Anfragen über Multithreading. Jeder Verbindung wird ein Thread zur Verarbeitung zugewiesen. Der Ausführungscode eines Prozesses muss jedoch seriell sein, aber jetzt muss er an einen Prozess übergeben werden wird verwendet, um den Parallelitätseffekt zu erreichen. Es gibt nur einen Hauptthread unter einem Prozess, was bedeutet, dass ein Thread verwendet wird, um den Parallelitätseffekt zu erreichen. Warum einen Prozess verwenden, um mehrere Parallelität zu erreichen, anstatt mehrere Threads zu verwenden, um mehrere Parallelität zu erreichen?
==========Antwort: Weil es effizienter ist, mehrere Parallelitäten in einem Prozess zu erreichen als in mehreren Threads, weil das Starten mehrerer Threads und die CPU viel Overhead verursachen erfordert Ständige Überprüfung des Status jedes Threads, um festzustellen, welcher Thread ausgeführt werden kann. Dies stellt auch eine Belastung für das System dar. Durch die Verwendung eines einzelnen Prozesses können dieser Mehraufwand und die Belastung des Systems vermieden werden.
Wie erreicht ein einzelner Prozess also mehrere Parallelitäten? ? ?
======== Antwort: Das Produzenten- und Konsumentenmodell (asynchron) kann durch Auswahl erreicht werden, die nicht blockiert. Der vorherige Socket-Prozess kann nur eine Verbindung empfangen und wird beim Empfang einer neuen Verbindung blockiert, da der Socket-Prozess zuerst mit dem Client kommunizieren muss und die beiden aufeinander warten [der Client sendet eine Nachricht, der Dienst Der Client empfängt es und der Client wartet auf die Rückkehr ... Der Server wartet auf den Empfang ...] Es wurde blockiert. Wenn zu diesem Zeitpunkt eine andere Verbindung besteht, muss er auf die vorherige Verbindung warten Um die Verbindung zu trennen, können Sie nur dann eine Verbindung herstellen. -----------Mit anderen Worten, die Verwendung eines Basis-Sockets zur Implementierung mehrerer Prozesse blockiert. Um dieses Problem zu lösen, wird ein Thread generiert Jede Verbindung wird nicht blockiert. Wenn zu viele Threads vorhanden sind, ist der Overhead und die Belastung der CPU relativ groß.) Wenn ein einzelner Socket blockiert ist, wartet er die meiste Zeit auf E/A-Vorgänge (Netzwerkvorgänge). sind auch IO-Operationen). Um diese Situation zu vermeiden, initiiert der Client eine Verbindung und registriert ein Dateihandle auf dem Server. Der Server fragt kontinuierlich die Liste dieser Dateihandles ab Stellt eine Verbindung mit dem Client her, ohne den Thread zu starten. Zu diesem Zeitpunkt interagiert der Hauptprozess mit dem Client, und andere Clients können keine Verbindung zum Hauptprozess herstellen, um zu erkennen, dass der Hauptprozess nicht nur Nachrichten an den Client senden und empfangen kann verbundener Client, aber auch beim Herstellen einer Verbindung mit einem neuen Client erfolgt die Abfrage sehr schnell (tote Schleife ), um die Liste der vom Client verbundenen Dateihandles zu aktualisieren, solange der Client eine Nachricht sendet. Der Server liest sie und gibt eine weitere Liste zum Empfangen der Nachricht an den Client zurück. Nach der Aktualisierung erfolgt die Kommunikation mit dem Client abgeschlossen, aber die Verbindung mit dem Client ist noch nicht unterbrochen, sondern trat dann in die nächste Abfrage ein. 】
select Vorteile
select wird derzeit auf fast allen Plattformen unterstützt und verfügt über eine gute plattformübergreifende Funktionalität.
select-Nachteile
Jedes Mal, wenn Sie select aufrufen, müssen Sie die fd-Sammlung vom Benutzermodus in den Kernelmodus kopieren. Dieser Overhead ist sehr groß, wenn es viele gibt fds
Es gibt eine maximale Grenze für die Anzahl der fds, die ein einzelner Prozess überwachen kann. Der Standardwert ist 1024 unter Linux (diese Grenze kann durch Ändern der Makrodefinition oder Neukompilierung des Kernels erhöht werden)
Und da der ausgewählte fd im Array platziert wird und das gesamte Array jedes Mal linear durchlaufen werden muss, ist bei vielen fd auch der Overhead sehr groß
Python select
Aufruf der -Funktion von select ist lesbar, beschreibbar, Ausnahmeal = select.select(rlist, wlist, xlist[, timeout]), die ersten drei Parameter sind jeweils drei Listen, und die Objekte im Array sind alle wartebar. Objekt: sind alle Dateibeschreibungen des Ganzzahlen-Deskriptors (Datei Deskriptor) oder ein Objekt mit einer Methode fileno(), die einen Dateideskriptor zurückgibt;
rlist: Liste wartet darauf, gelesen zu werden
wlist: Liste wartet auf Schreibbereitschaft
errlist: Wartet auf Liste „Ausnahme“
Auswahlmethode wird zur Überwachung des Dateideskriptors verwendet. Wenn sich der Deskriptor der Datei ändert, wird der Deskriptor erhalten.
1. Diese drei Listen können eine leere Liste sein, aber der Empfang von drei leeren Listen ist systemabhängig (akzeptabel unter Linux, aber nicht unter Windows).
2. Wenn der Deskriptor in der rlist-Sequenz lesbar ist (accetp und read), wird der geänderte Deskriptor abgerufen und zur lesbaren Sequenz hinzugefügt
3. Wenn die wlist-Sequenz vorhanden ist enthalten, werden alle Deskriptoren in der Sequenz zur beschreibbaren Sequenz hinzugefügt
4. Wenn im Handle in der Errlist-Sequenz ein Fehler auftritt, wird das Fehlerhandle zur Ausnahmesequenz hinzugefügt
5. Wenn das Timeout nicht festgelegt ist, blockiert select, bis sich der überwachte Deskriptor ändert.
Wenn das Timeout = 1 ist und keine Änderung im überwachten Handle vorliegt, blockiert select eine Sekunde lang und gibt dann drei Sekunden zurück leere Listen. Wenn sich der überwachte Deskriptor (fd) ändert, wird er direkt ausgeführt.
6. Ptython-Dateiobjekte (wie sys.stdin oder von open() und os.open() zurückgegebene Objekte) können in die Liste aufgenommen werden, und das Socket-Objekt gibt socket.socket() zurück. . Sie können die Klasse auch anpassen, solange es eine geeignete fileno()-Methode gibt (die tatsächlich einen Dateideskriptor und keine zufällige Ganzzahl zurückgeben muss).
select Beispiel:
Pythons select()-Methode ruft direkt die IO-Schnittstelle des Betriebssystems auf, die Sockets, geöffnete Dateien und Pipes überwacht (alle mit fileno( ) Dateihandle der Methode) lesbar und beschreibbar wird oder ein Kommunikationsfehler auftritt, erleichtert select() die gleichzeitige Überwachung mehrerer Verbindungen und ist effizienter als das Schreiben einer langen Schleife, um auf mehrere Clientverbindungen zu warten und diese zu überwachen. weil select Betrieb direkt über die vom Betriebssystem bereitgestellte C-Netzwerkschnittstelle und nicht über den Python-Interpreter
#coding:UTF8 import select import socket import sys import Queue #创建一个TCP/IP 进程 server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) server.setblocking(0) #连接地址和端口 server_address = ('localhost',10000) print >>sys.stderr,'starting up on %s prot %s' % server_address server.bind(server_address) #最大允许链接数 server.listen(5) inputs = [ server ] outputs = [] message_queues = {} while inputs: print >>sys.stderr,'\nwaiting for the next event' readable,writable,exceptional = select.select(inputs,outputs,inputs) # Handle inputs for s in readable: if s is server: # A "readable" server socket is ready to accept a connection connection, client_address = s.accept() print >>sys.stderr, 'new connection from', client_address #connection.setblocking(0) inputs.append(connection) # Give the connection a queue for data we want to send message_queues[connection] = Queue.Queue() else: data = s.recv(1024) if data: # A readable client socket has data print >>sys.stderr, 'received "%s" from %s' % (data, s.getpeername()) message_queues[s].put(data) #这个s相当于connection # Add output channel for response if s not in outputs: outputs.append(s) else: # Interpret empty result as closed connection print >>sys.stderr, 'closing', client_address, 'after reading no data' # Stop listening for input on the connection if s in outputs: outputs.remove(s) #既然客户端都断开了,我就不用再给它返回数据了,所以这时候如果这个客户端的连接对象还在outputs列表中,就把它删掉 inputs.remove(s) #inputs中也删除掉 s.close() #把这个连接关闭掉 # Remove message queue del message_queues[s] # Handle outputs for s in writable: try: next_msg = message_queues[s].get_nowait() except Queue.Empty: # No messages waiting so stop checking for writability. print >>sys.stderr, 'output queue for', s.getpeername(), 'is empty' outputs.remove(s) else: print >>sys.stderr, 'sending "%s" to %s' % (next_msg, s.getpeername()) s.send(next_msg.upper()) # Handle "exceptional conditions" for s in exceptional: print >>sys.stderr, 'handling exceptional condition for', s.getpeername() # Stop listening for input on the connection inputs.remove(s) if s in outputs: outputs.remove(s) s.close() # Remove message queue del message_queues[s] server
Code-Analyse:
select()-Methode empfängt und Überwachen Sie 3 Kommunikationslisten. Die erste Liste bezieht sich auf die von außen gesendeten Daten. Die zweite Liste dient der Überwachung und dem Empfang aller zu sendenden Daten (ausgehende Daten). . Information, als nächstes müssen wir 2 Listen erstellen, die Eingabe- und Ausgabeinformationen enthalten, die an select() übergeben werden sollen.
# Sockets, von denen wir erwarten, dass sie gelesen werden:inputs = [ server ]# Sockets, an die wir gehen to writeoutputs = [ ]zwischenspeichern in der Warteschlange darin speichern muss, und dann durch Auswählen herausnehmen und versenden.
# Warteschlangen für ausgehende Nachrichten (socket:Queue)message_queues = {}Hauptteil der Serverprogrammschleifen, calleing select() zum Blockieren und Warten auf Netzwerkaktivität.
Das Folgende ist die Hauptschleife dieses Programms. Wenn select() aufgerufen wird, blockiert es und wartet, bis neue Verbindungen und Daten eingehenwhile inputs: # Warten Sie, bis mindestens einer der Sockets zur Verarbeitung bereit ist. print >>sys.stderr, 'nwaiting for the next event' readable, beschreibbar, außergewöhnlich = select.select(inputs, outputs , Eingaben)Readable list 中的socket 可以有3种可能状态,第一种是如果这个socket是main "server" socket,它负责监听客户端的连接,如果这个main server socket出现在readable里,那代表这是server端已经ready来接收一个新的连接进来了,为了让这个main server能同时处理多个连接,在下面的代码里,我们把这个main server的socket设置为非阻塞模式。
第二种情况是这个socket是已经建立了的连接,它把数据发了过来,这个时候你就可以通过recv()来接收它发过来的数据,然后把接收到的数据放到queue里,这样你就可以把接收到的数据再传回给客户端了。
第三种情况就是这个客户端已经断开了,所以你再通过recv()接收到的数据就为空了,所以这个时候你就可以把这个跟客户端的连接关闭了。
对于writable list中的socket,也有几种状态,如果这个客户端连接在跟它对应的queue里有数据,就把这个数据取出来再发回给这个客户端,否则就把这个连接从output list中移除,这样下一次循环select()调用时检测到outputs list中没有这个连接,那就会认为这个连接还处于非活动状态
最后,如果在跟某个socket连接通信过程中出了错误,就把这个连接对象在inputs\outputs\message_queue中都删除,再把连接关闭掉
#coding:UTF8 import socket import sys messages = [ 'This is the message. ', 'It will be sent ', 'in parts.', ] server_address = ('localhost', 10003) # Create a TCP/IP socket socks = [ socket.socket(socket.AF_INET, socket.SOCK_STREAM), socket.socket(socket.AF_INET, socket.SOCK_STREAM), ] # Connect the socket to the port where the server is listening print >>sys.stderr, 'connecting to %s port %s' % server_address for s in socks: s.connect(server_address) for message in messages: # Send messages on both sockets for s in socks: print >>sys.stderr, '%s: sending "%s"' % (s.getsockname(), message) s.send(message) # Read responses on both sockets for s in socks: data = s.recv(1024) print >>sys.stderr, '%s: received "%s"' % (s.getsockname(), data) if not data: print >>sys.stderr, 'closing socket', s.getsockname() client
客户端程序展示了如何通过select()对socket进行管理并与多个连接同时进行交互,通过循环通过每个socket连接给server发送和接收数据。
server: starting up on localhost prot 10000 waiting for the next event new connection from ('127.0.0.1', 54812) waiting for the next event new connection from ('127.0.0.1', 54813) received "This is the message. " from ('127.0.0.1', 54812) waiting for the next event received "This is the message. " from ('127.0.0.1', 54813) sending "This is the message. " to ('127.0.0.1', 54812) waiting for the next event output queue for ('127.0.0.1', 54812) is empty sending "This is the message. " to ('127.0.0.1', 54813) waiting for the next event output queue for ('127.0.0.1', 54813) is empty waiting for the next event received "It will be sent " from ('127.0.0.1', 54812) received "It will be sent " from ('127.0.0.1', 54813) waiting for the next event sending "It will be sent " to ('127.0.0.1', 54812) sending "It will be sent " to ('127.0.0.1', 54813) waiting for the next event output queue for ('127.0.0.1', 54812) is empty output queue for ('127.0.0.1', 54813) is empty waiting for the next event received "in parts." from ('127.0.0.1', 54812) received "in parts." from ('127.0.0.1', 54813) waiting for the next event sending "in parts." to ('127.0.0.1', 54812) sending "in parts." to ('127.0.0.1', 54813) waiting for the next event output queue for ('127.0.0.1', 54812) is empty output queue for ('127.0.0.1', 54813) is empty waiting for the next event closing ('127.0.0.1', 54813) after reading no data closing ('127.0.0.1', 54813) after reading no data waiting for the next event client: connecting to localhost port 10000 ('127.0.0.1', 54812): sending "This is the message. " ('127.0.0.1', 54813): sending "This is the message. " ('127.0.0.1', 54812): received "THIS IS THE MESSAGE. " ('127.0.0.1', 54813): received "THIS IS THE MESSAGE. " ('127.0.0.1', 54812): sending "It will be sent " ('127.0.0.1', 54813): sending "It will be sent " ('127.0.0.1', 54812): received "IT WILL BE SENT " ('127.0.0.1', 54813): received "IT WILL BE SENT " ('127.0.0.1', 54812): sending "in parts." ('127.0.0.1', 54813): sending "in parts." ('127.0.0.1', 54812): received "IN PARTS." ('127.0.0.1', 54813): received "IN PARTS." 运行结果
poll
poll在1986年诞生于System V Release 3,它和select在本质上没有多大差别,但是poll没有最大文件描述符数量的限制。
poll和select同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。
另外,select()和poll()将就绪的文件描述符告诉进程后,如果进程没有对其进行IO操作,那么下次调用select()和poll() 的时候将再次报告这些文件描述符,所以它们一般不会丢失就绪的消息,这种方式称为水平触发(Level Triggered)。
在Python中调用poll
select.poll(),返回一个poll的对象,支持注册和注销文件描述符。
poll.register(fd[, eventmask])注册一个文件描述符,注册后,可以通过poll()方法来检查是否有对应的I/O事件发生。fd可以是i 个整数,或者有返回整数的fileno()方法对象。如果File对象实现了fileno(),也可以当作参数使用。
eventmask是一个你想去检查的事件类型,它可以是常量POLLIN, POLLPRI和 POLLOUT的组合。如果缺省,默认会去检查所有的3种事件类型。
事件常量 意义
POLLIN 有数据读取
POLLPRT 有数据紧急读取
POLLOUT 准备输出:输出不会阻塞
POLLERR 某些错误情况出现
POLLHUP 挂起
POLLNVAL 无效请求:描述无法打开
poll.modify(fd, eventmask) 修改一个已经存在的fd,和poll.register(fd, eventmask)有相同的作用。如果去尝试修改一个未经注册的fd,会引起一个errno为ENOENT的IOError。
poll.unregister(fd)从poll对象中注销一个fd。尝试去注销一个未经注册的fd,会引起KeyError。
poll.poll([timeout])去检测已经注册了的文件描述符。会返回一个可能为空的list,list中包含着(fd, event)这样的二元组。 fd是文件描述符, event是文件描述符对应的事件。如果返回的是一个空的list,则说明超时了且没有文件描述符有事件发生。timeout的单位是milliseconds,如果设置了timeout,系统将会等待对应的时间。如果timeout缺省或者是None,这个方法将会阻塞直到对应的poll对象有一个事件发生。
#coding: utf-8 import select, socket response = b"hello world" serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) serversocket.bind(('localhost', 10000)) serversocket.listen(1) serversocket.setblocking(0) # poll = select.poll() poll.register(serversocket.fileno(), select.POLLIN) connections = {} while True: for fd, event in poll.poll(): if event == select.POLLIN: if fd == serversocket.fileno(): con, addr = serversocket.accept() poll.register(con.fileno(), select.POLLIN) connections[con.fileno()] = con else: con = connections[fd] data = con.recv(1024) if data: poll.modify(con.fileno(), select.POLLOUT) elif event == select.POLLOUT: con = connections[fd] con.send(response) poll.unregister(con.fileno()) con.close()
epoll
直到Linux2.6才出现了由内核直接支持的实现方法,那就是epoll,它几乎具备了之前所说的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法。
epoll可以同时支持水平触发和边缘触发(Edge Triggered,只告诉进程哪些文件描述符刚刚变为就绪状态,它只说一遍,如果我们没有采取行动,那么它将不会再次告知,这种方式称为边缘触发),理论上边缘触发的性能要更高一些,但是代码实现相当复杂。
epoll同样只告知那些就绪的文件描述符,而且当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表 就绪描述符数量的值,你只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射(mmap)技术,这样便彻底省掉了 这些文件描述符在系统调用时复制的开销。
另一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描 述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调 机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。
在Python中调用epoll
select.epoll([sizehint=-1])返回一个epoll对象。
eventmask
事件常量 意义
EPOLLIN 读就绪
EPOLLOUT 写就绪
EPOLLPRI 有数据紧急读取
EPOLLERR assoc. fd有错误情况发生
EPOLLHUP assoc. fd发生挂起
EPOLLRT 设置边缘触发(ET)(默认的是水平触发)
EPOLLONESHOT 设置为 one-short 行为,一个事件(event)被拉出后,对应的fd在内部被禁用
EPOLLRDNORM 和 EPOLLIN 相等
EPOLLRDBAND 优先读取的数据带(data band)
EPOLLWRNORM 和 EPOLLOUT 相等
EPOLLWRBAND 优先写的数据带(data band)
EPOLLMSG 忽视
epoll.close()关闭epoll对象的文件描述符。
epoll.fileno返回control fd的文件描述符number。
epoll.fromfd(fd)用给予的fd来创建一个epoll对象。
epoll.register(fd[, eventmask])在epoll对象中注册一个文件描述符。(如果文件描述符已经存在,将会引起一个IOError)
epoll.modify(fd, eventmask)修改一个已经注册的文件描述符。
epoll.unregister(fd)注销一个文件描述符。
epoll.poll(timeout=-1[, maxevnets=-1])等待事件,timeout(float)的单位是秒(second)。
#coding:Utf8 import socket, select EOL1 = b'\n\n' EOL2 = b'\n\r\n' response = b'HTTP/1.0 200 OK\r\nDate: Mon, 1 Jan 1996 01:01:01 GMT\r\n' response += b'Content-Type: text/plain\r\nContent-Length: 13\r\n\r\n' response += b'Hello, world!' serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) serversocket.bind(('localhost', 10000)) serversocket.listen(1) serversocket.setblocking(0) epoll = select.epoll() epoll.register(serversocket.fileno(), select.EPOLLIN) try: connections = {}; requests = {}; responses = {} while True: events = epoll.poll(1) for fileno, event in events: if fileno == serversocket.fileno(): connection, address = serversocket.accept() connection.setblocking(0) epoll.register(connection.fileno(), select.EPOLLIN) connections[connection.fileno()] = connection requests[connection.fileno()] = b'' responses[connection.fileno()] = response elif event & select.EPOLLIN: requests[fileno] += connections[fileno].recv(1024) if EOL1 in requests[fileno] or EOL2 in requests[fileno]: epoll.modify(fileno, select.EPOLLOUT) print('-'*40 + '\n' + requests[fileno].decode()[:-2]) elif event & select.EPOLLOUT: byteswritten = connections[fileno].send(responses[fileno]) responses[fileno] = responses[fileno][byteswritten:] if len(responses[fileno]) == 0: epoll.modify(fileno, 0) connections[fileno].shutdown(socket.SHUT_RDWR) elif event & select.EPOLLHUP: epoll.unregister(fileno) connections[fileno].close() del connections[fileno] finally: epoll.unregister(serversocket.fileno()) epoll.close() serversocket.close()
Das obige ist der detaillierte Inhalt vonDetaillierte Beschreibung der Analyse der Python-Select-Epoll-Umfrage. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!