如何运行程序并在从网络接收数据时执行代码?

时间:2014-05-12 21:11:01

标签: python multithreading network-programming twisted

我在Python中编写了一个基本上执行以下操作的小程序:

  1. 获取用户输入的热门词汇。如果它与set关键字匹配则继续。
  2. 输入正确的热门词后,系统会要求用户输入命令。
  3. 在读取命令后,progam会检查命令文件以查看是否有与该输入匹配的命令
  4. 如果找到匹配的命令,请执行该命令所说的任何内容。
  5. 我想添加通过网络执行命令的功能,如下所示(并学会在途中使用Twisted):

    1. 客户端#1输入针对客户端#2的命令。
    2. 该命令被发送到服务器,该服务器将其路由到客户端#2。
    3. 客户端#2接收命令并在其有效时执行。
    4. 注意:应该可以在本地(使用下面的代码)和远程输入命令。

      经过一番思考后,我无法想出任何其他方法来实现这个:

      1. 将上述程序作为进程#1(我在开头编写的本地运行程序)运行。
      2. Twisted客户端将作为进程#2运行,并从远程客户端接收命令。每当收到命令时,Twisted客户端将初始化一个线程,该线程将解析命令,检查其有效性并在其有效时执行。
      3. 由于我没有那么多线程经验,也没有网络编程经验,我想不出任何其他对我有意义的方案。

        上述方案是否过于复杂? 在尝试以这种方式实现之前,我将不胜感激。

        python程序的代码(没有客户端)是:

        main(这是start()方法):

        class Controller:
            def __init__(self,listener, executor):
                self.listener = listener
                self.executor = executor
        
        
            def start(self):
                while True:
                    text = self.listener.listen_for_hotword()
        
                    if self.executor.is_hotword(text):
                        text = self.listener.listen_for_command()
                        if self.executor.has_matching_command(text):
                            self.executor.execute_command(text)
                        else:
                            tts.say("No command found. Please try again")
        

        监听器(从用户那里获得输入):

        class TextListener(Listener):
            def listen_for_hotword(self):
                text = raw_input("Hotword: ")
                text =' '.join(text.split()).lower()
                return text
        
            def listen_for_command(self):
                text = raw_input("What would you like me to do: ")
                text = ' '.join(text.split()).lower()
                return text
        

        执行程序(执行给定命令的类):

        class Executor:
            #TODO: Define default path
            def __init__(self,parser, audio_path='../Misc/audio.wav'):
                self.command_parser = parser
                self.audio_path = audio_path
        
            def is_hotword(self,hotword):
                return self.command_parser.is_hotword(hotword)
        
            def has_matching_command(self,command):
                return self.command_parser.has_matching_command(command)
        
            def execute_command(self,command):
                val = self.command_parser.getCommand(command)
                print val
                val = os.system(val) #So we don't see the return value of the command
        

        命令文件解析器:

        class KeyNotFoundException(Exception):
            pass
        
        
        class YAMLParser:
            THRESHOLD = 0.6
        
            def __init__(self,path='Configurations/commands.yaml'):
                with open(path,'r') as f:
                    self.parsed_yaml = yaml.load(f)
        
            def getCommand(self,key):
                try:
                    matching_command = self.find_matching_command(key)
                    return self.parsed_yaml["Commands"][matching_command]
                except KeyError:
                    raise KeyNotFoundException("No key matching {}".format(key))
        
            def has_matching_command(self,key):
                try:
                    for command in self.parsed_yaml["Commands"]:
                        if jellyfish.jaro_distance(command,key) >=self.THRESHOLD:
                            return True
                except KeyError:
                    return False
        
            def find_matching_command(self,key):
                for command in self.parsed_yaml["Commands"]:
                    if jellyfish.jaro_distance(command,key) >=0.5:
                        return command
        
            def is_hotword(self,hotword):
                return jellyfish.jaro_distance(self.parsed_yaml["Hotword"],hotword)>=self.THRESHOLD
        

        配置文件示例:

        Commands:
          echo : echo hello
        
        
        Hotword: start
        

1 个答案:

答案 0 :(得分:4)

我发现在你的问题中找到背景非常困难,但我会自己去解决问题。

如何运行程序并在从网络接收数据时执行代码?

正如你在问题中所提到的,写一个"步行和嚼口香糖的典型方法" style app是以线程或事件循环样式设计代码。

鉴于你在谈论线程和扭曲(这是事件循环风格),我担心你可能会考虑混合两者。

我认为它们是根本不同的编程风格(每个都有他们擅长的地方),混合它们通常是通往地狱的道路。

让我给你一些解释背景

背景:线程与事件编程

线程

  • 如何思考这个概念:

      

    我需要同时做多件事,并且我希望我的操作系统能够计算运行这些单独任务的方式和时间

  • 加号:

    • ' '让一个程序同时使用多个处理器核心的方法

      在posix世界中, only 让一个进程同时在多个CPU核心上运行的方式是通过线程(典型的理想线程数不再是给定的核心)机)

    • 更容易入手

      您运行内联的相同代码通常无需重新设计即可被抛入线程中(如果没有GIL,则需要进行一些锁定,但稍后会更多)

    • 更容易使用会占用你可以给他们的所有CPU的任务

      即。在大多数情况下,使用线程解决方案然后使用事件/异步框架,数学方式更容易处理

  • 劣势:

    • Python对线程有一个特殊问题

      在CPython中,global interpreter lock(GIL)可以否定线程执行多任务的能力(使线程几乎无用)。 Avoiding the GIL很麻烦,可以撤消在线程中使用的所有易用性

    • 当您添加线程(和锁定)时,事情变得复杂,请参阅此SO:Threading Best Practices

    • 非常适合IO /用户交互任务

      虽然线程非常擅长处理少量想要使用大量CPU的任务(理想情况下每个核心一个线程),但它们在处理大部分时间等待的大量任务时却不太理想。

  • 最好用:

      

    计算成本高昂的东西。

    如果你想要同时运行大量的数学运算,那么你就不太可能比操作系统更智能地调度CPU利用率。

    (考虑到CPythons GIL问题,线程不应该手动用于数学,而应该使用内部线程的库(如NumPy))

事件循环/异步编程

  • 如何思考这个概念:

      

    我需要同时做多件事,但我(程序员)希望直接控制/直接暗示我的子任务运行的方式和时间

  • 您应该如何考虑代码:

      

    将所有子任务想象成一个交织在一起的整体,你的思想应该总是想到"这段代码运行得足够快,以至于它不会搞砸其他子任务我&#34 39;管理"

  • 加号:

    • 网络/ IO / UI连接非常高效,包括大量连接

      事件循环风格程序是解决c10k problem的关键技术之一。像Twisted这样的框架可以在小型机器上运行的一个python进程中处理数万个连接。

    • 随着其他连接/任务的添加,可预测的(小的)复杂性增加

      虽然存在相当陡峭的学习曲线(特别是在扭曲中),但一旦了解了基础知识,就可以将新的连接类型添加到项目中,同时最大限度地提高整体复杂性。从提供键盘接口的程序转移到提供键盘/ telnet / web / ssl / ssh连接的程序可能只是每个接口的几行胶水代码(...这是由框架高度可变,但无论如何框架事件循环复杂性更可预测,然后线程随着连接数的增加而增加)

  • 劣势:

    • 开始更难。

      事件/异步编程需要与第一行代码不同的设计风格(尽管您会发现设计风格在某种程度上可以从语言移植到语言)

    • 一个事件循环,一个核心

      虽然事件循环可以让您同时处理大量的IO连接,但它们自身并不能同时在多个核心上运行。解决这个问题的传统方法是编写程序,使程序的多个实例可以同时运行,每个核心一个(此技术绕过GIL问题)

    • 多路复用高CPU任务可能很困难

      事件编程需要将高CPU任务分成几部分,以使每个部分占用(理想情况下)少量的CPU,否则每当高CPU任务运行时事件系统就会停止多任务。

  • 最好用:

      

    基于IO的内容

TL; DR - 鉴于您的问题,请查看twisted和-not-threads

虽然您的应用程序似乎不是基于IO的,但它似乎都不是基于CPU的(看起来您通过system调用当前播放的音频,system旋转每次调用它都是一个独立的进程,所以它的工作不会烧掉你的进程CPU - 尽管system阻塞,所以它是一个禁止扭曲的 - 你必须在扭曲中使用不同的调用。)

您的问题也并不表示您担心最大限度地使用多个核心。

因此,鉴于您特别谈到Twisted,并且事件循环解决方案似乎是您的应用程序的最佳匹配,我建议您查看Twisted和-not-threads。

鉴于'最佳用途'上面列出的你可能会想到混合扭曲和线程是得到的方法,但是如果你做任何事情,甚至稍微错误,你将失去事件循环的优点(你会阻止)和线程化(GIL不会让线程多任务)并且拥有超级复杂的东西,这对你没有任何好处。

上述方案是否过于复杂?在尝试以这种方式实现之前,我将不胜感激。

&#39>计划'你给的是:

  

经过一番思考后我无法想出任何其他方式来实现这个目的:

     
      
  1. 将上述程序作为进程#1运行(在我开始编写的本地运行程序)。
  2.   
  3. Twisted客户端将作为进程#2运行,并从远程客户端接收命令。每当收到命令时,Twisted客户端将初始化一个线程,该线程将解析命令,检查其有效性并在其有效时执行。
  4.         

    由于我没有那么多线程经验,也没有网络编程经验,我无法想到任何其他对我有意义的方案。

回答"计划是否......过于复杂",我几乎肯定会说是因为你谈论扭曲的线程。 (见tr; dr)

鉴于我对你想要构建的东西的肯定不完整(和困惑)的理解,我会想象一个扭曲的解决方案,你会看起来像:

  1. 仔细研究krondo Twisted Introduction(它确实需要逐行跟踪示例代码,但如果你做的工作是一个AWESOME学习工具,用于扭曲和一般的事件编程)
  2. 从头开始,你重写了你的热门词汇'使用你在krondo指南中学到的东西扭曲的东西 - 开始只提供你现在拥有的任何界面(键盘?)
  3. 为该代码添加其他通信接口(telnet,web等),这样您就可以访问为键盘(?)界面编写的处理代码。
  4. 如果你在问题中说明,你真的需要一台服务器,你可以编写第二个扭曲的程序来提供(你可以在krondo指南中看到所有这些的例子)。虽然我猜你什么时候理解扭曲的图书馆支持,但你会发现你不必构建任何额外的服务器,你可以在你的基本代码中包含你需要的任何协议。