我在Python中创建一个简单的TCP服务器 - 客户端脚本。服务器是线程化的,并为每个客户端连接分配一个新的worker / thread。到目前为止,我已经完成了整个服务器模块的编码。但是我的函数叫handle_clients()
,它为每个传入的客户端连接分叉,这个函数变得非常长。为了提高代码的可读性,我想将handle_clients()
分成多个小函数。我理解当我将handle_client()
拆分为较小的函数时,拆分函数应该包含mutex locks
以同步多个handle_clients()
线程之间的共享使用。这样做实际上会降低程序的效率,因为handle_clients()
必须等待其他线程在实际使用之前解锁共享函数。我的另一个想法是在handle_clients()
线程中创建这些较小的函数作为线程。并等待这些线程在继续之前使用Thread.join()
完成。有更好的方法吗?
我的代码:
#!/usr/bin/python
import socket
import threading
import pandas as pd
class TCPServer(object):
NUMBER_OF_THREADS = 0
BUFFER = 4096
threads_list = []
def __init__(self, port, hostname):
self.socket = socket.socket(
family=socket.AF_INET, type=socket.SOCK_STREAM)
self.socket.bind((hostname, port))
def listen_for_clients(self):
self.socket.listen(5)
while True:
client, address = self.socket.accept()
client_ID = client.recv(TCPServer.BUFFER)
print(f'Connected to client: {client_ID}')
if client_ID:
TCPServer.NUMBER_OF_THREADS = TCPServer.NUMBER_OF_THREADS + 1
thread = threading.Thread(
target=TCPServer.create_worker, args=(self, client, address, client_ID))
TCPServer.threads_list.append(thread)
thread.start()
if TCPServer.NUMBER_OF_THREADS > 2:
break
TCPServer.wait_for_workers()
def wait_for_workers():
for thread in TCPServer.threads_list:
thread.join()
def create_worker(self, client, address, client_ID):
print(f'Spawned a new worker for {client_ID}. Worker #: {TCPServer.NUMBER_OF_THREADS}')
data_list = []
data_frame = pd.DataFrame()
client.send("SEND_REQUEST_TYPE".encode())
request_type = client.recv(TCPServer.BUFFER).decode('utf-8')
if request_type == 'KMEANS':
print(f'Client: REQUEST_TYPE {request_type}')
client.send("SEND_DATA".encode())
while True:
data = client.recv(TCPServer.BUFFER).decode('utf-8')
if data == 'ROW':
client.send("OK".encode())
while True:
data = client.recv(TCPServer.BUFFER).decode('utf-8')
print(f'Client: {data}')
if data == 'ROW_END':
print('Data received: ', data_list)
series = pd.Series(data_list)
data_frame.append(series, ignore_index=True)
data_list = []
client.send("OK".encode())
break
else:
data_list.append(int(data))
client.send("OK".encode())
elif data == 'DATA_END':
client.send("WAIT".encode())
# (Vino) pass data to algorithm
print('Data received from client {client_ID}: ', data_frame)
elif request_type == 'NEURALNET':
pass
elif request_type == 'LINRIGRESSION':
pass
elif request_type == 'LOGRIGRESSION':
pass
def main():
port = input("Port: ")
server = TCPServer(port=int(port), hostname='localhost')
server.listen_for_clients()
if __name__ == '__main__':
main()
注意:以下代码块是重复的,并且将在handle_client()
函数中多次使用。
while True:
data = client.recv(TCPServer.BUFFER).decode('utf-8')
if data == 'ROW':
client.send("OK".encode())
while True:
data = client.recv(TCPServer.BUFFER).decode('utf-8')
print(f'Client: {data}')
if data == 'ROW_END':
print('Data received: ', data_list)
series = pd.Series(data_list)
data_frame.append(series, ignore_index=True)
data_list = []
client.send("OK".encode())
break
else:
data_list.append(int(data))
client.send("OK".encode())
elif data == 'DATA_END':
client.send("WAIT".encode())
# (Vino) pass data to algorithm
print('Data received from client {client_ID}: ', data_frame)
这是我想在一个单独的函数中放置一个块的块,并在handle_client()
线程中调用它。
答案 0 :(得分:1)
你的代码已经很久了,我不会深入研究它,但要尽量保持一般。
我明白当我将handle_client()拆分成较小的函数时,split函数应该包含在互斥锁中。
这不是直接的,在线程之间你必须使用锁来防止内存覆盖,无法调用你的函数。
服务器已线程化
看起来您正在进行CPU密集型工作(我看到LINALG
,NEURALNET
,...),在Python中使用线程调度CPU是不合逻辑的密集加载,因为GIL将线程化线程之间的CPU使用。
在Python中并行化CPU密集型工作的方法是使用进程。
进程不共享内存,因此您可以在没有互斥锁的情况下自由操作变量,但它们根本不会被共享,我希望您的工作是独立的,因为它们无法共享任何州。
如果你需要分享状态,避免锁定,处理起来很复杂,它是死锁的方式,而且它不可读,试着实现你的"国家共享"使用队列,作为一个作业管道,每个工作人员从队列中拉出来,做工作,并推送到另一个队列,这样可以使事情清晰易懂。此外还有线程和进程队列的实现,因此您几乎可以无缝切换。
如果TCPServer.NUMBER_OF_THREADS> 2: 破
嘿,当你有两个以上的线程,现有你的主要进程,杀死你的服务器时,你正在打破你的主循环,我打赌你现在想要的。哦,如果你使用进程而不是线程,你应该预先创建它们的池,因为它们的创建成本高于线程。并重用它们,一个进程可以在完成一个进程后完成工作,它不必死(通常使用queues将作业发送到您的进程)。
旁注:我使用HTTP而不是原始TCP来实现这一点,以便从请求,响应,错误报告,现有框架以及使用现有客户端的能力(命令行中的curl / wget)中受益。您的浏览器,Python中的requests
。我完全异步地实现这个(没有阻塞HTTP请求),比如一个创建作业的请求,以及跟踪获取状态和结果的请求,如:
$ curl -X POST http://localhost/linalg/jobs/ -d '{your data}'
201 Created
Location: http://localhost/linalg/jobs/1
$ curl -XGET http://localhost/linalg/jobs/1
200 OK
{"status": "queued"}
一段时间后......
$ curl -XGET http://localhost/linalg/jobs/1
200 OK
{"status": "in progress"}
一段时间后......
$ curl -XGET http://localhost/linalg/jobs/1
200 OK
{"status": "done", "result": "..."}