代码之家  ›  专栏  ›  技术社区  ›  fnclovers

为什么Python中的一个线程对共享变量所做的更改对其他线程不可见?

  •  1
  • fnclovers  · 技术社区  · 2 年前

    我正试图在多个线程中使用同一个变量,但变量的值并没有在线程间一致更新。例如,当线程1将变量更新为1时,线程2不会识别此更改,而是看到旧值。

    这里有一个简单的代码示例来说明这个问题。当用户按下“a”键时,变量“QUERY”应更新并显示如下:

    1. 查询1:a
    2. 查询:a

    但是,我得到的实际输出只有:

    1. 查询1:a

    你能帮我理解为什么会发生这种情况以及如何解决吗?

    import getch
    import threading
    
    QUERY = ""
    EXIT_THREAD = False
    def input_thread():
        global EXIT_THREAD
        last_query = ""
        while not EXIT_THREAD:
            if last_query != QUERY:
                last_query = QUERY
                print(f"Query: {QUERY}")
    
    thread = threading.Thread(target=input_thread)
    thread.start()
    while True:
        char = getch.getch()
        if char == "\n":
            break
        elif char == "\x7f":
            QUERY = QUERY[:-1]
        else:
            QUERY += char
        print(f"Query1: {QUERY}")
    # kill input thread
    EXIT_THREAD = True
    thread.join()
    
    3 回复  |  直到 2 年前
        1
  •  1
  •   ShadowRanger    2 年前

    问题是 getch 模块编码不正确。它不会释放 the GIL (全局解释器锁定)当它阻止等待输入时,所以你的其他线程不会 允许 跑步。与锁同步并没有多大帮助;GIL已经在保护对的访问 QUERY .

    问题是每次更改后 查询 ,你回到 getch ,锁定GIL。什么时候 getch 返回时,GIL已经过了足够的时间 立即 ,另一个线程检查更改,看到 最后的 更改并报告它,主线程最终会重新获得控制权,但通常做得不够,无法在之前引起另一次GIL切换 getch 再次锁定它,另一个线程就再也没有机会运行并看到更改,直到 getch 下次返回。这可能会因Python版本而异(检查GIL的规则不时发生变化),但它总是很不稳定。

    正确的解决方案是 getch 模块在进行阻塞调用之前在内部释放GIL,但如果失败,您可以在每次调用之前故意以GIL释放的方式进行阻塞,从而给另一个线程一些时间来运行 getch 调用,通过导入 time 模块,并添加睡眠以给其他线程时间查看最新的更改:

    while True:
        time.sleep(0.001)  # Explicitly releases GIL for a millisecond
        char = getch.getch()
    

    这得到了你所期望的行为,并且在技术上受比赛条件的限制 另外 涉及到线程,对于像这样的两个线程来说,这是非常可靠的。

        2
  •  0
  •   Daniel Viglione    2 年前

    尝试使用同步两个线程 .

    lock = threading.Lock()
    
    ...
    
    def input_thread():
        global EXIT_THREAD
        last_query = ""
        while not EXIT_THREAD:
            with lock:
                if last_query != QUERY:
                    last_query = QUERY
                    print(f'Query: {QUERY}')
    
    thread = threading.Thread(target=input_thread)
    thread.start()
    
    while True:
        char = getch.getch()
        if char == "\n":
            break
        elif char == "\x7f":
            with lock:
                QUERY = QUERY[:-1]
        else:
            with lock:
                QUERY += char
        print(f"Query1: {QUERY}")
    
        3
  •  0
  •   fnclovers    2 年前

    而不是使用原件 getch 实现,我实现了 getch 来解决这个问题。

    以下是工作代码:

    import threading
    import sys
    import termios
    
    def getch():
        fd = sys.stdin.fileno()
        old_settings = termios.tcgetattr(fd)
        term = termios.tcgetattr(fd)
        try:
            term[3] &= ~(termios.ICANON | termios.ECHO | termios.IGNBRK | termios.BRKINT)
            termios.tcsetattr(fd, termios.TCSAFLUSH, term)
    
            ch = sys.stdin.read(1)
        finally:
            termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
        return ch
    
    QUERY = ""
    EXIT_THREAD = False
    def input_thread():
        global EXIT_THREAD
        last_query = ""
        while not EXIT_THREAD:
            if last_query != QUERY:
                last_query = QUERY
                print(f"Query: {QUERY}")
    
    thread = threading.Thread(target=input_thread)
    thread.start()
    while True:
        char = getch()
        if char == "\n":
            break
        elif char == "\x7f":
            QUERY = QUERY[:-1]
        else:
            QUERY += char
        print(f"Query1: {QUERY}")
    # kill input thread
    EXIT_THREAD = True
    thread.join()