【问题标题】:How to use the threading module to call a function?如何使用线程模块调用函数?
【发布时间】:2018-04-11 22:46:48
【问题描述】:

或者换句话说,如何创建延时函数?
我有一个 python 机器人,它应该在使用某些命令时向用户的关注者发送通知。

例如,如果 Tim 运行命令 '>follow Tom',所有 Tim 的追随者都会在 PM 中收到他关注 Tom 的通知,并且 Tom 会收到 Tim 关注了他的通知。

我已经用大量的追随者测试了这个功能,并且机器人保持稳定并且避免被从服务器踢出,我猜是因为 for 循环增加了发送给每个追随者的每条消息的延迟。

我遇到的问题是,如果两个用户要同时运行一个保证通知的命令。机器人立即被踢下线。所以我需要的是在通知功能运行之前添加一个人为的延迟。 Time.sleep() ,不起作用。它所做的一切都是冻结整个程序,并将每个命令都保存在队列中。 (如果两个用户运行 >follow ,它将休眠 2 秒,并在延迟后运行他们的两个命令)

我正在尝试使用线程模块来替换 time.sleep()。我的通知功能如下。

#message is the message to be sent, var is the username to use
def notify(message,var):
      #SQL connect 
      dbconfig = read_db_config()
      conn = MySQLConnection(**dbconfig)
      cursor = conn.cursor()
      #choose all of user's followers
      cursor.execute('select username from users where notifications=0 and username IN (select follower from followers where followed like "{}")'.format(var))
      results = cursor.fetchall()
      #for each , send a PM
      for result in results:
        self.pm.message(ch.User(str(result[0])), message)
      conn.close()  

那么我将如何使用线程来做到这一点?我已经尝试了几种方法,但让我们选择最糟糕的一种。

def example(_):
    username = 'bob'
    # _ is equal to args
    a = notify("{} is now following {}.".format(username,_),username)
    c =threading.Timer(2,a)
    c.start()

这将引发 Nonetype 错误作为响应。

线程 Thread-1 中的异常:回溯(最近一次调用最后一次):
文件“/usr/lib/python2.7/threading.py”,第 810 行,在 __bootstrap_inner self.run() 文件“/usr/lib/python2.7/threading.py”,第 1082 行,运行中 self.function(*self.args, **self.kwargs) TypeError: 'NoneType' object is not callable

注意:我认为这个方法会起作用,会有很多用户同时使用这个机器人,所以在它崩溃之前这似乎是一个修复。

【问题讨论】:

  • hmmm... 这些正在使用 time.sleep 这似乎对我不起作用.. 还记得它是一个机器人,所以当它在 time.sleep() 中时,它所做的只是冻结机器人并且不允许它被使用。请求只是堆积如山。我需要任何其他请求都必须等到时间结束,然后才会开始延迟。
  • 不是让notify() 通过调用self.pm.message() 直接写入消息,而是让它将消息写入队列。让另一个线程从该队列中读取,等待一些延迟,然后实际发送消息。
  • 换句话说,在for循环中,对于每一个找到的用户,将每一个结果放入一个队列,并为每一个创建一个延迟?我还会为那个模块或另一个模块使用线程吗?
  • 您遇到了什么错误?看起来你可能需要一个互斥锁?

标签: python mysql multithreading


【解决方案1】:

我会尝试使用像我在下面写的类那样的线程锁。

这将导致在任何给定时间只有一个线程能够发送 PM。

class NotifyUsers():
    def __init__(self, *args, **kwargs):
        self.notify_lock = threading.Lock()
        self.dbconfig = read_db_config()
        self.conn = MySQLConnection(**dbconfig)
        self.cursor = self.conn.cursor()

    def notify_lock_wrapper(self, message, var):
        self.notify_lock.acquire()
        try:
            self._notify(message, var)
        except:
            # Error handling here
            pass
        finally:
            self.notify_lock.release()

    def _notify(self, message, var):
        #choose all of user's followers
        self.cursor.execute('select username from users where notifications=0 and username IN (select follower from followers where followed like "{}")'.format(var))
        results = self.cursor.fetchall()

        #for each, send a PM
        for result in results:
            self.pm.message(ch.User(str(result[0])), message)

【讨论】:

    【解决方案2】:

    这里有一些代码可能会有所帮助。请注意 notify 处理结果的方式发生了变化。

    import threading
    import Queue
    
    def notifier(nq):
        # Read from queue until None is put on queue.
        while True:
            t = nq.get()
            try:
                if t is None:
                    break
                func, args = t
                func(*args)
                time.sleep(2) # wait 2 seconds before sending another notice
            finally:
                nq.task_done()
    
    
    # message is the message to be sent, var is the username to use, nq is the
    # queue to put notification on.
    def notify(message, var, nq):
        #SQL connect
        dbconfig = read_db_config()
        conn = MySQLConnection(**dbconfig)
        cursor = conn.cursor()
        #choose all of user's followers
        cursor.execute('select username from users where notifications=0 and username IN (select follower from followers where followed like "{}")'.format(var))
        results = cursor.fetchall()
        #for each , send a PM
        for result in results:
            # Put the function to call and its args on the queue.
            args = (ch.User(str(result[0])), message)
            nq.put((self.pm.message, args))
        conn.close()
    
    
    if __name__ == '__main__':
        # Start a thread to read from the queue.
        nq = Queue.Queue()
        th = threading.Thread(target=notifier, args=(nq,))
        th.daemon = True
        th.start()
        # Run bot code
        # ...
        #
        nq.put(None)
        nq.join() # block until all tasks are done
    

    【讨论】:

    • 您在 th.start() 下放置的间隙应该是机器人代码的其余部分?
    • 这将是您调用/启动任何代码体调用 notify() 的地方。这都可以放入另一个启动/初始化函数中,但在主要部分中作为示例。关键是首先启动处理程序线程,然后再运行其余的机器人代码。
    • 好的,看来我仍然需要延迟,因为它会读取队列并在多次请求后仍然会踢机器人
    • @Pacified - 修复了包含延迟的答案
    • 因此在每条消息之间添加了延迟并且延迟加起来,我在 30 秒内没有收到机器人的响应。然后它一次发送所有请求并被踢。
    猜你喜欢
    • 2015-07-17
    • 1970-01-01
    • 1970-01-01
    • 2020-04-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-10
    • 1970-01-01
    相关资源
    最近更新 更多