python多线程实战——进程与线程
在项目开发过程中,总结出关于多线程的知识与经验,供开发者参考
线程 vs 进程
在os课程中,关于线程和进程的区别讲法过于“学院派”,很难直观体会。在实际开发的具体场景中,这种区别其实很明显:
进程--以terminal的视角来看
从一个特别直观的角度出发,在vscode的终端中,不同的终端天然地区分了不同进程(当然一个终端可以有多进程,以及后台进程)
在这个终端中运行 python test.py,就开启了一个python进程;如果点击[+],新建一个终端,重新运行python test.py,开启的是全新的python进程
由这个方式,我受到启发,如果我在python脚本内部,通过os的命令行来运行另外一个python脚本(如 cmd = ["python", "test.py"]),就可以通过一个进程启动另一个进程了
不过,涉及多进程的内容,不在本章节中做过多讨论,上述内容更重要的是和多线程进行区分
线程
线程的启动方式就比较简单了,只需要定义一个函数def xxx_thread(),然后用python的threading库启动就行
def monitor_loop():
pass
import threading
monitor_thread = threading.Thread(target=monitor_loop)
monitor_thread.start()
...
通信
进程通信
进程之间的通信是非常麻烦的,不同的进程内部信息不共享,如
# 定义一个数据缓存
class Cache:
def __init__(self):
self._record = {}
def get(self, key):
return self._record.get(key)
def put(self, key, value):
self._record[key] = value
CACHE = cache() # 单例
假设我有一个listener进程,负责监听启动信号req和启动信息meta; 一个worker进程负责工作
如果我想通过cache来传递信息,如:
# listener进程
import subprocess
cache = CACHE
... # 接收信号的功能
req = message.get('req')
meta = message.get('meta')
if req == 'start':
cache.put('meta',meta)
cmd = ["python", "worker.py"]
subprocess.run(cmd)
# worker进程
meta = cache.get('meta')
print(meta)
... # 工作的功能
线程通信
而线程不存在这个问题,如:
# listener进程
cache = CACHE
... # 接收信号的功能
req = message.get('req')
meta = message.get('meta')
def worker():
meta = cache.get('meta')
print(meta)
... # 工作的功能
if req == 'start':
cache.put('meta',meta)
worker_thread = threading.Thread(target=worker)
worker_thread.start()
多线程通信
不同的线程,可以通过主线程中的共享变量实现通信,如定义的缓存CACHE:
# listener进程
cache = CACHE
... # 接收信号的功能
req = message.get('req')
meta = message.get('meta')
def worker1():
meta = cache.get('meta')
... # 工作的功能
def worker2():
cache.get('meta')
... # 工作的功能
if req == 'start':
cache.put('meta',meta)
worker_thread1 = threading.Thread(worker1)
worker_thread2 = threading.Thread(worker2)
worker_thread1.start()
worker_thread2.start()
线程的创建与销毁
在 Python 中,可以通过threading.Thread对象来创建新线程。每个线程都是由主进程统一调度和管理的,因此线程的生命周期是受到进程生命周期的约束的。当进程结束时,其下所有线程都会被强制终止。
线程的创建
创建线程时,需要指定线程要运行的目标函数(target),并可通过args/kwargs传入参数。例如:
import threading
def my_worker(name):
print(f"{name} is working...")
# 创建线程
t = threading.Thread(target=my_worker, args=("worker1",))
t.start()
.start()方法启动线程,主线程会异步地启动新线程来执行目标函数。
线程的结束
1. 主动结束:线程的函数执行完毕,线程就会自动退出,无需手动销毁。
2. 被动结束:如果主进程退出,其下所有子线程会被强制关闭(包括未完成的daemon线程)。
3. 守护线程(daemon):可以设置线程为守护线程daemon=True,此时主进程退出时守护线程会直接随之结束;而非守护线程会阻止主进程退出,直到它们运行结束。
t = threading.Thread(target=my_worker, args=("daemon_worker",), daemon=True)
t.start()
注意,线程没有t.stop()方法,强行中断线程可能导致资源的泄露(如文件写到一半被中断,且仍然保持打开状态)