Python多线程存在print指令时线程未完成任务的原因分析
问题描述
我尝试在Python中并行化一个简单任务,编写的代码如下:
import threading def function(dictionary): if threading.currentThread().name == "Thread 1": print("thread 1...") for i in sorted(dictionary)[0: int(len(dictionary) / 2)]: dictionary[i] = sum(dictionary[i]) if threading.currentThread().name == "Thread 2": print("thread 2...") for i in sorted(dictionary)[int(len(dictionary) / 2):]: dictionary[i] = sum(dictionary[i]) dictionary = {} dictionary["k1"] = [1, 1] dictionary["k2"] = [1, 1, 1] dictionary["k3"] = [1, 1, 1, 1] dictionary["k4"] = [1] dictionary["k5"] = [1, 1, 1] dictionary["k6"] = [1, 1] dictionary["k7"] = [1, 1, 1] dictionary["k8"] = [1, 1, 1, 1, 1] t1 = threading.Thread(name="Thread 1", target=function, args=(dictionary,)) t2 = threading.Thread(name="Thread 2", target=function, args=(dictionary,)) t1.start() t2.start() print(dictionary)
实际运行输出结果为:
thread 1... thread 2... {'k1': 2, 'k2': 3, 'k3': 4, 'k4': 1, 'k5': [1, 1, 1], 'k6': [1, 1], 'k7': [1, 1, 1], 'k8': [1, 1, 1, 1, 1]}
预期输出结果应为:
{'k1': 2, 'k2': 3, 'k3': 4, 'k4': 1, 'k5': 3, 'k6': 2, 'k7': 3, 'k8': 5}
目前只有移除代码中的print指令时,才能正常得到预期结果,请问该现象的产生原因是什么?
问题根源
这个现象和print本身没有任何本质关系,核心原因是主线程根本没等两个子线程跑完任务,就直接读取并打印了共享字典。代码里的print语句只是改变了线程调度的时序,才让你误以为“移除print就能正常运行”。
具体的执行逻辑拆解:
- 你在调用
t1.start()、t2.start()启动两个子线程后,没有搭配join()方法阻塞等待子线程执行结束,主线程启动完子线程就会立刻往下执行print(dictionary)的逻辑。 - Python的多线程执行依赖GIL(全局解释锁)轮流分配执行权,控制台打印这类IO操作会主动触发GIL释放,让操作系统重新调度线程的执行顺序:
- 当子线程代码里保留
print时,子线程执行打印操作会主动释放GIL,此时主线程很容易抢到执行权,赶在Thread 2开始循环处理后半段字典键值的求和逻辑前,就直接读取了字典内容。这也是你看到字典后半段的值还是原始列表、没被替换成求和结果的直接原因。 - 当你把子线程里的print删掉后,两个子线程从启动到跑完字典处理循环的耗时极短,主线程还没来得及抢到执行权执行打印逻辑,两个子线程就已经完成了全部字典更新,此时才会看起来“得到了预期结果”。
- 当子线程代码里保留
- 本质上这是典型的线程竞态问题:主线程和两个子线程并发读写同一个共享字典,没有加任何同步逻辑,最终打印的字典状态完全取决于CPU随机调度的顺序。就算你删掉所有print,只要运行次数足够多,依然会遇到主线程抢跑、打印出还没处理完成的字典的情况。
修复方式
在主线程打印字典前,显式等待两个子线程执行完成,在t2.start()的代码后面追加两行:
t1.join() t2.join()
加完后主线程会一直阻塞,直到Thread 1和Thread 2全部跑完字典的求和逻辑,再执行打印操作,就能稳定得到预期的结果。
内容的提问来源于stack exchange,提问作者Chariot
相关产品推荐
相关产品推荐

