(Python)使用线程通过getch监测键盘输入

4 投票
1 回答
3857 浏览
提问于 2025-04-18 09:36

我一直在尝试写一段测试代码,这段代码会不停地打印“正在运行”,直到我按下一个键。我试着通过创建一个额外的线程(叫做thread1)来实现这个功能,让它监听键盘输入。

当我运行我的代码时,线程启动得很好,似乎也正常执行,直到调用getch.getch()。在getch.getch()等待键盘输入的时候,不仅thread1停止了,主线程也停下来了。

我该如何确保在thread1监听键盘输入的同时,主线程也能继续运行呢?

我使用的是python 2.7和getch 1.0(https://pypi.python.org/pypi/getch)。

这是我的代码:

import threading
import time
import getch

class myThread (threading.Thread):
    def __init__(self, threadID, name, cont):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.cont = cont

    def run(self):
        print "Starting " + self.name +"\n"
        print "waiting 2 seconds"
        time.sleep(2)
        char = getch.getch()
        print 'You pressed %s' % char
        cont.append(1)
        print "Terminating" + self.name

cont = []

thread1 = myThread(1, "Thread1", cont)

thread1.start()

while cont == []:
    print "Running"
    time.sleep(0.5)

它输出这个:

Starting Thread1
Running

waiting 2 seconds
Running
Running
Running

而且它会一直停在那里,直到我按下一个键

1 个回答

4

你遇到这个问题是因为有个叫做GIL的东西。用threading.Thread的时候会出问题,但如果你换成multiprocessing.Process就没事了:

class myThread (multiprocessing.Process):
    def __init__(self, threadID, name, cont):
        super(myThread, self).__init__()
        #threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.cont = contdef run(self):
        print "Starting " + self.name +"\n"
        char = getch.getch()
        print 'You pressed %s' % char
        cont.append(1)
        print "Terminating" + self.name

cont = []

thread1 = myThread(1, "Thread1", cont)

thread1.start()

while cont == []: 
    print "Running"
    time.sleep(0.5)

输出结果:

dan@dantop:~$ ./get.py 
Running
Starting Thread1

Running
Running
Running
Running
Running
Running
Running
Running
You pressed f
TerminatingThread1
Running
Running
Running
Running
Running
Running
Running
Running

getch是一个C语言扩展,它在调用getchar()的时候会阻塞,但它没有先释放GIL。因为Python其实不能同时运行两个线程,所以它会在工作线程里卡住,等着getchar()的阻塞调用结束。

其实你可以很简单地修复这个问题,只需要在getch的C扩展代码中明确释放GIL,使用Py_BEGIN_THREADSPy_ALLOW_THREADS

static PyObject *getch_getche(PyObject *self, PyObject *args)
{
    int ok = PyArg_ParseTuple(args, "");
    char c;
    Py_BEGIN_ALLOW_THREADS
    c = getche();
    Py_END_ALLOW_THREADS
    return PyUnicode_FromFormat("%c", c); 
}

static PyObject *getch_getch(PyObject *self, PyObject *args)
{
    int ok = PyArg_ParseTuple(args, "");
    char c;
    Py_BEGIN_ALLOW_THREADS
    c = getch();
    Py_END_ALLOW_THREADS
    return PyUnicode_FromFormat("%c", c); 
}

如果你对getchmodule.c做了这个修改并重新编译扩展,原来的使用线程的示例代码就能正常工作了。

撰写回答