python - 进程与线程 -2
1. 线程
一个进程可以包含多个线程,一条线程就是进程中一个单一顺序的控制流。一个进程中可以并发多个线程,每条线程执行不同的任务。
1.1 使用Thread创建线程
import threading
import time
def task():
for i in range(3):
time.sleep(1)
print(f"线程名:{threading.current_thread().name}")
if __name__ == "__main__":
print("主线程开始")
# 创建线程对象,放到ts中
ts = []
for i in range(5):
p = threading.Thread(target=task)
ts.append(p)
# 启动线程
for i in ts:
i.start()
# 等待
for i in ts:
i.join()
print("主线程结束")
Thread(group=None, target=None, name=None,
args=(), kwargs=None, *, daemon=None)
- group 保留
- target 表示一个可调用对象,经程启动时,run方法会调用此对象。
- name 线程名
- args, kwargs是传递给target函数的参数
:: 方法和Process类基本一样。
1.2 使用Thread子类创建线程
继承Thread类,重写run方法即可。
import threading
import time
class SubThread(threading.Thread):
# 重写
def run(self):
for i in range(3):
time.sleep(1)
print(f"线程名:{threading.current_thread().name}")
if __name__ == "__main__":
print("主线程开始")
# 创建线程对象,放到ts中
ts = []
for i in range(5):
p = SubThread()
ts.append(p)
# 启动线程
for i in ts:
i.start()
# 等待
for i in ts:
i.join()
print("主线程结束")
2. 线程之间通信
和进程不同,多个线程可以访问进程中定义的全局变量。即在一个进程中的所有线程共享全局变量。
2.1 互斥锁
由于多个线程共享全局变量,在同一时刻都可以修改它,造成混乱。
互斥锁:防止多个线程同时读取某一块内存区域。互斥锁为资源引入一个状态:锁定和非锁定。某个线程要修改共享数据时,先将其锁定,此时资源的状态为锁定,其它线程不能更改。该线程用完释放资源,将资源状态变成非锁定,其它线程才可以锁定资源进行修改。
互斥锁保证了每次只有一个线程可以进行写入操作,保证数据的正确性。
import time
from multiprocessing import Lock
from threading import Thread
# 100张票
n = 100
def task():
global n
# 上锁
lock.acquire()
n -= 1
time.sleep(0.1)
print(f"购票成功,还剩下{n}张")
# 解锁
lock.release()
if __name__ == '__main__':
# 创建一把锁
lock = Lock()
cache = []
# 创建20个线程来买票
for i in range(20):
t = Thread(target=task)
t.start()
for i in cache:
i.join()
如果不上锁就混乱了,sleep之后,n的结果全是80,因为那个时间段n的值已经是80了。
在使用互斥锁时,要避免死锁。一般有多个锁时会造成死锁,甲等乙放锁,乙等甲放锁,都不放卡住了。
2.2 用队列在线程间通信
与队列在进程间通信一样,但用的Queue是queue模块的,而不是multiprocessing模块中的Queue
import random
import time
from queue import Queue
from threading import Thread
class Producer(Thread):
def __init__(self, q):
Thread.__init__(self)
self.q = q
def run(self):
for i in range(5):
print(f"生产了{i}")
self.q.put(i)
time.sleep(random.random())
print("下班")
class Consumer(Thread):
def __init__(self, q):
Thread.__init__(self)
self.q = q
def run(self):
for i in range(5):
v = self.q.get()
print(f"消费了{i}")
self.q.put(i)
time.sleep(random.random())
if __name__ == '__main__':
q = Queue()
p1 = Producer(q)
p2 = Consumer(q)
p1.start()
p2.start()
3.线程池
线程池的基类是 concurrent.futures 模块中的 Executor,Executor 提供了两个子类,即 ThreadPoolExecutor 和 ProcessPoolExecutor,其中 ThreadPoolExecutor 用于创建线程池,而 ProcessPoolExecutor 用于创建进程池。
- ThreadPoolExecutor 线程池
- ProcessPoolExecutor 进程池
如果使用线程池来管理并发编程,那么只要将相应的 task 函数提交给线程池,剩下的事情就由线程池来搞定。
Exectuor 提供了如下常用方法:
submit(fn, *args, **kwargs)
:将fn
函数提交给线程池。*args
代表传给fn
函数的参数,*kwargs
代表以关键字参数的形式为fn
函数传入参数。map(func, *iterables, timeout=None, chunksize=1)
:该函数类似于全局函数map(func, *iterables)
,只是该函数将会启动多个线程,以异步方式立即对iterables
执行map
处理。shutdown(wait=True)
:关闭线程池。
程序将 task 函数提交(submit)给线程池后,submit 方法会返回一个 Future 对象,Future 类主要用于获取线程任务函数的返回值。由于线程任务会在新线程中以异步方式执行,因此,线程执行的函数相当于一个“将来完成”的任务,所以 Python 使用 Future 来代表。
Future 提供了如下方法:
cancel()
:取消该 Future 代表的线程任务。如果该任务正在执行,不可取消,则该方法返回 False;否则,程序会取消该任务,并返回 True。cancelled()
:返回 Future 代表的线程任务是否被成功取消。running()
:如果该 Future 代表的线程任务正在执行、不可被取消,该方法返回 True。done()
:如果该 Funture 代表的线程任务被成功取消或执行完成,则该方法返回 True。result(timeout=None)
:获取该 Future 代表的线程任务最后返回的结果。如果 Future 代表的线程任务还未完成,该方法将会阻塞当前线程,其中 timeout 参数指定最多阻塞多少秒。exception(timeout=None)
:获取该 Future 代表的线程任务所引发的异常。如果该任务成功完成,没有异常,则该方法返回 None。add_done_callback(fn)
:为该 Future 代表的线程任务注册一个“回调函数”,当该任务成功完成时,程序会自动触发该 fn 函数。
在用完一个线程池后,应该调用该线程池的 shutdown() 方法,该方法将启动线程池的关闭序列。调用 shutdown() 方法后的线程池不再接收新任务,但会将以前所有的已提交任务执行完成。当线程池中的所有任务都执行完成后,该线程池中的所有线程都会死亡。
3.1 使用线程池来执行线程任务的步骤
- 调用 ThreadPoolExecutor 类的构造器创建一个线程池。
- 定义一个普通函数作为线程任务。
- 调用 ThreadPoolExecutor 对象的 submit() 方法来提交线程任务。
- 当不想提交任何任务时,调用 ThreadPoolExecutor 对象的 shutdown() 方法来关闭线程池。
import threading
import time
from concurrent.futures import ThreadPoolExecutor
def task():
print(f"线程{threading.current_thread().name}")
time.sleep(1)
return "end"
pool = ThreadPoolExecutor(max_workers=4,thread_name_prefix="Thread ")
for i in range(10):
future = pool.submit(task)
pool.shutdown(wait=True)
3.2 获取执行结果
pool = ThreadPoolExecutor(max_workers=4, thread_name_prefix="Thread ")
for i in range(10):
future = pool.submit(task)
print(future.result())
pool.shutdown(wait=True)
future.result(), 获取结果, 如果 Future 代表的线程任务还未完成,该方法将会阻塞当前线程, 所以和单线程没啥区别了。
用add_done_callback回调:
如果程序不希望直接调用 result() 方法阻塞线程,则可通过 Future 的 add_done_callback() 方法来添加回调函数。当线程任务完成后,程序会自动触发该回调函数,并将对应的 Future 对象作为参数传给该回调函数。
直接调用result函数结果
from concurrent.futures import ThreadPoolExecutor
def task():
print(f"线程{threading.current_thread().name}")
time.sleep(1)
return "end"
def callback_result(future):
print(future.result())
pool = ThreadPoolExecutor(max_workers=4, thread_name_prefix="Thread ")
for i in range(10):
future = pool.submit(task)
future.add_done_callback(callback_result)
pool.shutdown(wait=True)
另外,由于线程池实现了上下文管理协议(Context Manage Protocol),因此,程序可以使用 with 语句来管理线程池,这样即可避免手动关闭线程池。如:
with ThreadPoolExecutor(max_workers=4, thread_name_prefix="Thread ") as pool:
for i in range(10):
future = pool.submit(task)
future.add_done_callback(callback_result)
可以把future对象放到一个列表里,之后用as_completed()来获取结果。
flist = []
with ThreadPoolExecutor(max_workers=4, thread_name_prefix="Thread ") as pool:
for i in range(10):
future = pool.submit(task)
flist.append(future)
# 阻塞,哪个任务先完成,就返回给主线程
for future in as_completed(flist):
print(future.result())
as_completed() 方法是一个生成器,在没有任务完成的时候,会一直阻塞,除非设置了 timeout。当有某个任务完成的时候,会 yield 这个任务,就能执行 for 循环下面的语句,然后继续阻塞住,循环到所有的任务结束。同时,先完成的任务会先返回给主线程。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?