如何修复这个脚本,使其不会导致CPU占用过高?

8

在我的家用Kubuntu机器上,我运行了一个脚本,在每次按键时都会发出蜂鸣声,无论哪个窗口或应用程序具有焦点,该脚本改编自这个富有见地的页面

#!/usr/bin/env python 

from Xlib.display import Display
import os
import sys

ZERO=[]
for i in range(0,32):
        ZERO.append(0)
ignorelist=[ZERO]

def main():    
        if os.getuid()==0:
                os.system("modprobe pcspkr")
                print("Speaker enabled, start as normal user")
                sys.exit()

        print("If no beep is heard, then run as root to enable pcspkr")

        disp = Display()
        while 1:
                keymap=disp.query_keymap()
                if keymap not in ignorelist:
                        os.system("beep")

if __name__ == '__main__':
        main()

这个脚本运行得很好,但它将我的双核Intel机器的两个CPU占用率都保持在约80%左右,因此我几乎不能在机器上做其他事情。如何降低这个简单脚本的CPU要求而不干扰其操作?换句话说,无论哪个窗口或应用程序具有焦点,它仍然应该在按键时发出蜂鸣声。

如果Python中不可能实现这一点,那么我应该看看哪些其他技术?C语言?我会认为存在某些内核组件,可以通知应用程序按键:否则KDE如何处理全局快捷方式?我怎样才能使我的应用程序也接收到这些通知呢?

目标是在每按下一个键时发出蜂鸣声,因为我正在训练手指在机械键盘上打字,既不底部出击,也不错过按键。我刚从Cherry Browns转到Cherry Blues,缺乏触觉反馈需要一些时间来适应。

请注意,任何解决方案都必须发出蜂鸣声,无论哪个窗口具有焦点。此程序旨在作为后台守护程序运行在我使用的所有应用程序中。

谢谢。


你检查过哪些程序占用了CPU吗?似乎很难相信像这样的单线程程序能够独自承担责任。 - Mark Ransom
是的,我已经检查了,杀掉这个脚本可以消除 CPU 负载。重新启动它会再次创建 CPU 负载。有趣的是,反复按一个键进行嗡嗡声实际上会减少负载。 - dotancohen
5个回答

12

使用事件:

import Xlib
from Xlib.display import Display

display = Display()

screen = display.screen()
w = screen.root.create_window(0, 0, 100, 100, 1,
              screen.root_depth,
              event_mask = Xlib.X.KeyPressMask)
w.map()

while True:
    event = display.next_event()
    if event.type != Xlib.X.KeyPress:
        continue
    print "OHAI"

细节从http://python-xlib.sourceforge.net/doc/html/python-xlib_10.html#SEC9搬运而来


1
谢谢。只需将if keycode_requires_beep(event.detail)替换为else:并将# beep替换为os.system("beep"),脚本就可以运行且不会卡住CPU,但按键时不会有蜂鸣声。我本来期望会有很多的蜂鸣声,因为我使用的是简单的else,但没有任何蜂鸣声!你有什么想法为什么会这样吗? - dotancohen
2
我不知道,但是有一些问题可以帮助你找到答案。1:你是否进入了哔哔声部分,但当你到达那里时没有听到哔声?2:你是否在按键时接收到事件?3:该事件的类型是什么?当你知道你预期发生的事情与你看到的不符时,所有这些问题都是相当标准的调试方法。其思想是持续地询问“实际发生了什么?”和“我预期发生了什么?”直到你找到你的期望与破碎代码残酷的现实不符的地方。 - Iain
谢谢。我发现在event = Display.next_event()这行的前面打印print("1")可以正常输出,但是在该行后面打印print("2")就无法输出了。看起来它似乎卡在了那一行代码上。 - dotancohen
1
好的,那真的很快就变得混乱了。你的问题原因就在文档中:「为了避免向客户端发送他们不感兴趣的事件,他们必须明确告诉服务器他们感兴趣的事件。」这是在创建窗口时完成的,这意味着我们需要创建一个窗口。我已经用一个实际测试过的代码替换了示例代码。两个更改是前奏部分获取显示并创建窗口,以及更正比较以检查事件类型。 - Iain
谢谢。不幸的是,这种方法只在新窗口被聚焦时才有效。显然,这对我日常使用没有帮助。除了“screen”之外,我没有看到其他合适的选项。还有其他选择吗?否则,这本来是一个好的解决方案。无论如何,我从这篇文章中学到了很多。谢谢。 - dotancohen
嗯,我不知道。screen.root是一个窗口,你可能可以在它上面调用change_attributes(event_mask = Xlib.X.KeyPressMask),但你仍然不会拥有这个窗口,所以我不知道你是否能够获取事件。http://lwn.net/Articles/101475/执行了screen.root.query_tree(),对每个子窗口调用child.select_input(Xlib.X.KeyPressMask),然后进行递归。如果你懂C语言,那么只需要对该代码进行小的更改就可以到达你想要的位置。 - Iain

8

你的while循环会占用所有CPU周期,因为它会尽可能地快速执行。你可以添加一个小的sleep()延迟,但这样有可能会错过一些关键事件。或者,考虑使用pyhook模块来监控按键。


谢谢。我实际上使用的是Kubuntu Linux,而不是Windows。不过找得好。 - dotancohen

7
您可以使用nice命令启动脚本,此命令会降低脚本的优先级,因此只有在系统没有其他任务时才运行。这样它仍将占用CPU周期,但您将能够正常使用系统完成其他任务。
详细信息请参见手册页。
编辑: 为了减少CPU使用率,您可以添加一个小的延迟,使用time.sleep(0.01)。这将减少CPU负载,但会略微增加按键和哔声之间的时间间隔。

谢谢。使用nice -n 19启动应用程序仍会占用CPU,但系统至少更具响应性。这种方法的缺点是仍然保持CPU监视器被占用,因此它变得无法确定不相关的系统负载。此外,当CPU处于活动状态时,温度仍然很高。因此,这是一个不错的解决方法,但我真的需要解决根本问题的东西。 - dotancohen
2
Lanaru的第一个建议是添加一个小的睡眠延迟。如果您使用time.sleep(0.05),则在按键和蜂鸣之间最多会有50毫秒的延迟。这将大大减少CPU负载。 - Hans Then
1
为什么要踩我?我很想知道我的回答哪里出了问题。 - Hans Then
很好的解决方案只针对CPU密集型进程,以避免破坏交互性。sleep也不是真正的解决方案,因为它只会使代码错过按键。这是一个IO密集型进程;原始代码使用了错误的X11调用,只查询键盘状态,真正的解决方案是使用阻塞式调用。 - Antti Haapala -- Слава Україні
这是一个艰难的决定,这里有一些很棒的答案。我从中学到了很多。最终,最好的妥协似乎是在while循环中加入一个小的0.01延迟,并慷慨地使用“nice”。这样可以减少应用程序使用的资源,并在其他应用程序需要时放弃它所使用的资源,但仍然可以在所有应用程序中提供实时按键通知。谢谢! - dotancohen
显示剩余3条评论

5
您的程序占用了CPU,因为它正在运行一个无限循环,即使没有按键被按下,也会让CPU每毫秒检查一次键盘状态。由于您的计算机没有指示可以停止,因此它将尽可能经常地进行检查,从而消耗资源。
正如Iain所建议的那样,解决这个问题最优雅的方法是使用Display.next_event(),它会告诉程序等待直到接收到新事件(例如按键)。在等待时间内,您的程序不会消耗大量的CPU资源,负载应该会显著降低。

谢谢。不幸的是,Display.next_event() 似乎需要它自己的窗口,并且仅适用于该窗口内的按键。我需要一个全局解决方案。 - dotancohen

4

此前发布的问题的答案中得知,有一个名为pyxhook的Python模块是pykeylogger程序的一部分。

使用这个pyxhook模块,您可以使用以下代码在每次按键时发出蜂鸣声:

import pyxhook
import time
import os
import sys

def do_beep(event):
    os.system('beep')

hm = pyxhook.HookManager()
hm.HookKeyboard()
hm.KeyDown = do_beep
hm.start()
while True:
    try:
        time.sleep(10)
    except: 
        break
hm.cancel()

我无法进行适当的测试,因为我没有 beep 命令。但是,它会为每个按键报告 sh: beep: command not found

感谢您找到了适用于Linux的pyhook版本!这个解决方案对CPU来说更加合理,但它会排队蜂鸣命令,这些命令运行得太快,因此不适合实时打字指示。 - dotancohen
你可以尝试使用os.system('beep&')在后台运行每个蜂鸣声,但我不确定它们之间会如何交互。此外,一些版本的beep命令还允许自定义蜂鸣声 - 例如缩短持续时间。 - nandhp
那是个好主意。实际上,我已经试图改变蜂鸣声,但Kubuntu似乎没有承认这种变化 - dotancohen
1
您似乎正在更改X11响铃设置,然后使用“beep”命令触发响铃。我认为“beep”命令直接与PC扬声器交互,因此可能完全忽略您的X11响铃设置。请尝试使用“beep”命令本身的选项(http://manpages.ubuntu.com/manpages/precise/en/man1/beep.1.html)。 “Beep”手册有几个示例,例如“beep-f 400 -l 10”。还要注意,“beep”具有“-s”和“-c”选项,您可以使用这些选项避免为每个按键启动新的“beep”进程。这也可能有所帮助(略微)减少延迟。 - nandhp
1
谢谢nandhp!我甚至没有想到使用beep标志。 - dotancohen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接