在线程中调用函数

时间:2017-11-05 08:43:23

标签: python multithreading

我在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()线程中调用它。

1 个答案:

答案 0 :(得分:1)

你的代码已经很久了,我不会深入研究它,但要尽量保持一般。

  

我明白当我将handle_client()拆分成较小的函数时,split函数应该包含在互斥锁中。

这不是直接的,在线程之间你必须使用锁来防止内存覆盖,无法调用你的函数。

  

服务器已线程化

看起来您正在进行CPU密集型工作(我看到LINALGNEURALNET,...),在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": "..."}

要实现这一点,已经完成了许多不错的工作,通常是aiohttpapistar,等等。