需要帮助学习使用非线程安全代码的Python

问题描述 投票:0回答:1

我正在尝试学习 Python 3.12.2 的教程。我正在尝试演示非线程安全代码的部分。该教程表示以下代码将产生不可预测的结果。嗯,对我来说,它产生了非常可预测的结果。代码是:

# when no thread synchronization used

from threading import Thread as Thread

def inc():
    global x
    for _ in range(1000000):
         x+=1
 
#global variable
x = 0
counter = 0
while counter < 10:
    # creating threads
    threads = [Thread(target=inc) for _ in range(10)]

    # start the threads
    for thread in threads:
        thread.start()

    #wait for the threads
    for thread in threads:
        thread. Join()

    print("Pass ", counter, "final value of x:", f"{x:,}")
    x = 0
    counter += 1

它产生了以下输出;

PS D:\PythonDev> python .\thread3a.py
Pass  0 final value of x: 10,000,000
Pass  1 final value of x: 10,000,000
Pass  2 final value of x: 10,000,000
Pass  3 final value of x: 10,000,000
Pass  4 final value of x: 10,000,000
Pass  5 final value of x: 10,000,000
Pass  6 final value of x: 10,000,000
Pass  7 final value of x: 10,000,000
Pass  8 final value of x: 10,000,000
Pass  9 final value of x: 10,000,000
PS D:\PythonDev>

我修改了它以添加外部 while 循环,这样我就不必一遍又一遍地从命令行运行它。根据教程,每次传递的预期结果应该是 10,000,000。然而,实际上结果应该是不可预测的并且小于10,000,000。两者都不是。我做错了什么?

我的环境是;

O/S: MS Windows 10 Home 22H2
RAM: 16 GB
CPU: Intel Core I7-2860QM
Terminal Session: PowerShell 7.4.1
Python version: 3.12.2 
python multithreading
1个回答
0
投票

尽管 Tim Roberts 发表了评论,但操作

x += 1
不是原子的;它由多个 Python 字节码操作组成(请参见下面的偏移量 12 到 18):

>>> import dis
>>> def inc():
...     global x
...     for _ in range(1000000):
...          x+=1
...
>>> dis.dis(inc)
  3           0 LOAD_GLOBAL              0 (range)
              2 LOAD_CONST               1 (1000000)
              4 CALL_FUNCTION            1
              6 GET_ITER
        >>    8 FOR_ITER                12 (to 22)
             10 STORE_FAST               0 (_)

  4          12 LOAD_GLOBAL              1 (x)
             14 LOAD_CONST               2 (1)
             16 INPLACE_ADD
             18 STORE_GLOBAL             1 (x)
             20 JUMP_ABSOLUTE            8
        >>   22 LOAD_CONST               0 (None)
             24 RETURN_VALUE
>>>

因此,当每个线程的时间片结束时,它将在任意字节码指令处被中断。当我运行代码时(用

thread. Join()
替换
thread.join()
后)我得到:

Pass  0 final value of x: 6,688,134
Pass  1 final value of x: 6,096,719
Pass  2 final value of x: 6,250,393
Pass  3 final value of x: 6,116,210
Pass  4 final value of x: 6,686,225
Pass  5 final value of x: 4,912,244
Pass  6 final value of x: 4,965,819
Pass  7 final value of x: 6,301,143
Pass  8 final value of x: 6,549,947
Pass  9 final value of x: 7,321,995

如果您有...

from threading import Thread as Thread, Lock

lock = Lock()

def inc():
    global x
    for _ in range(1000000):
        with lock:
            x+=1

...

...每次迭代你都会得到 10,000,000 -- 但代码运行速度要慢得多。

© www.soinside.com 2019 - 2024. All rights reserved.