如何在 Python 中以交错方式执行线程

How to execute threads in an interleaved way in Python

我几乎是 Python 线程方面的新手。 这是我的代码,以 GeeksForFeeks 上的示例代码为模型,该代码解释了使用锁的线程行为。 但结果 - 对我来说 - 是违反直觉的。

import threading 

# global variable x 
x = 0

# creating a lock 
lock = threading.Lock() 

def increment(): 
    global x 
    x += 1
    print("thread1:", x)
    
def decrement(): 
    global x 
    x -= 1
    print("thread2:", x)
  
def plus(): 
    global lock
    for _ in range(100000): 
        lock.acquire() 
        increment()
        lock.release()

def minus(): 
    global lock
    for _ in range(100000): 
        lock.acquire() 
        decrement()
        lock.release()
  
def main_task(): 
    global x 
    # setting global variable x as 0 
    x = 0

    # creating threads 
    t1 = threading.Thread(target=plus) 
    t2 = threading.Thread(target=minus)
  
    # start threads 
    t1.start() 
    t2.start() 
  
    # wait until threads finish their job 
    t1.join() 
    t2.join() 
  
if __name__ == "__main__": 
        main_task()

我希望得到这样的结果:

thread1: 1
thread2: 0
thread1: 1
thread2: 0
thread1: 1
thread2: 0
thread1: 1
...

但我得到了

thread1: 1
thread1: 2
thread1: 3
thread1: 4
thread1: 5
thread1: 6
thread1: 7
...
thread1: 151
thread2: 150
thread2: 149
thread2: 148
thread2: 147
thread2: 146
thread2: 145
thread2: 144
thread2: 143
thread2: 142
thread2: 141
...

为什么thread2每次从thread1释放时都无法获取到锁?

我错过了什么?

发生这种情况至少有两个原因:

  1. GIL 是解释器本身的单个锁,它添加了一条规则,即执行任何 Python 字节码都需要获取解释器锁。

  2. 更笼统:Lock 对象不太适合您。是的,它同步了对变量的访问,但是在 lock 释放之后和获取它之前,其他代码可以执行多次这样的迭代。

对于交错同步,最好使用Condition对象,它允许代码等待其他代码通知(本质上是唤醒它)第一个可以执行:

# creating a lock
lock = threading.Condition()

# ...

def plus():
    global lock
    with lock:
        for _ in range(100000):
            increment()
            lock.notify()
            lock.wait()
        lock.notify()  # notify to finish companion thread


def minus():
    global lock
    with lock:
        for _ in range(100000):
            decrement()
            lock.notify()
            lock.wait()
        lock.notify()  # notify to finish companion thread
...
thread1: 1
thread2: 0
thread1: 1
thread2: 0
thread1: 1
thread2: 0