跳到主要内容

线程(选修)

线程

线程是系统调度资源的最小单位(CPU通过计时器来切换线程)

在Python中,同个时间只有一个线程在运行

当然,如果你运行大量的I/O任务,多进程依然是最好的选择

线程数等于 CPU 内核数的两倍是最高效的。

GIL 是一个防止多个线程同时执行 Python 字节码的互斥锁。之所以需要这种锁,主要是因为 CPython 的内存管理不是线程安全的

在这种环境下,GIL 限制解释器本身只能有一个线程运行,而且任何 Python 解释器级别的操作都是序列化的,因此任何时候都只能有一条语句抛出异常。与异常相关的共享变量也因此受到保护。

线程间通信的目的主要是为了线程同步,因此线程没有像进程通信那样用于数据交换的通信机制。

Python的标准库提供了两个模块:_thread和threading,_thread是低级模块,threading是高级模块,对_thread进行了封装。绝大多数情况下,我们只需要使用threading这个高级模块。

threading:Threading Multiprocessing Module Code Documentation

多线程


import time
import threading


def test_thread(para='hi', sleep=3):
time.sleep(sleep)
print(para)


def main():
# create thread
thread_hi = threading.Thread(target=test_thread)
thread_hello = threading.Thread(target=test_thread, args=('hello', 1))
# run thread
thread_hi.start()
thread_hello.start()
print('Main thread has ended!')


if __name__ == '__main__':
main()

如下所示的界面中,有“下载”和“关于”两个按钮,用休眠的方式模拟点击“下载”按钮会联网下载文件需要耗费10秒的时间,当点击“下载”按钮后,整个任务阻塞:

import time
import tkinter
import tkinter.messagebox


def download():
# 模拟下载任务需要花费5秒钟时间
time.sleep(5)
tkinter.messagebox.showinfo('提示', '下载完成!')


def show_about():
tkinter.messagebox.showinfo('关于', '作者: 123(v1.0)')


def main():
top = tkinter.Tk()
top.title('单线程')
top.geometry('400x400')
top.wm_attributes('-topmost', True)

panel = tkinter.Frame(top)
button1 = tkinter.Button(panel, text='下载', command=download)
button1.pack(side='left')
button2 = tkinter.Button(panel, text='关于', command=show_about)
button2.pack(side='right')
panel.pack(side='bottom')

tkinter.mainloop()


if __name__ == '__main__':
main()

使用多线程后,不会阻塞了主线程:

import time
import tkinter
import tkinter.messagebox
from threading import Thread


def main():

class DownloadTaskHandler(Thread):

def run(self):
time.sleep(5)
tkinter.messagebox.showinfo('提示', '下载完成!')
# 启用下载按钮
button1.config(state=tkinter.NORMAL)

def download():
# 禁用下载按钮
button1.config(state=tkinter.DISABLED)
# 通过daemon参数将线程设置为守护线程(主程序退出就不再保留执行)
# 在线程中处理耗时间的下载任务
DownloadTaskHandler(daemon=True).start()

def show_about():
tkinter.messagebox.showinfo('关于', '作者: 123(v1.0)')

top = tkinter.Tk()
top.title('多线程')
top.geometry('400x400')
top.wm_attributes('-topmost', 1)

panel = tkinter.Frame(top)
button1 = tkinter.Button(panel, text='下载', command=download)
button1.pack(side='left')
button2 = tkinter.Button(panel, text='关于', command=show_about)
button2.pack(side='right')
panel.pack(side='bottom')

tkinter.mainloop()


if __name__ == '__main__':
main()

会看到弹出的窗口是多模态的,点击下载按钮不影响其他按钮操作。

Python的多线程并不能发挥CPU的多核特性,这一点只要启动几个执行死循环的线程就可以得到证实了。之所以如此,是因为Python的解释器有一个“全局解释器锁”(GIL)的东西,任何线程执行前必须先获得GIL锁,然后每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行,这是一个历史遗留问题。

Python解释器由于设计时有GIL全局锁,导致了多线程无法利用多核。多线程的并发在Python中就是一个美丽的梦。

多进程是有效的。