threading #
提示
concurrent.futures.ThreadPoolExecutor提供了一个更高级的接口,可将任务推送到后台线程,而不会阻塞调用线程的执行,并且可以在需要时检索结果。
此外,queue提供了一个线程安全的接口,用于线程之间的数据交换。
asyncio提供了一种无需使用多个操作系统线程即可实现任务级并发的替代方法。
CPython实现细节:在 CPython 中,由于全局解释器锁(Global Interpreter Lock, GIL),一次只能有一个线程执行Python代码(尽管某些面向性能的库可能可以克服此限制)。如果希望应用程序更好地利用多核计算机的计算资源,建议考虑使用 multiprocessing或concurrent.futures.ProcessPoolExecutor。但是,如果希望同时运行多个IO密集型任务,threading仍然是一个合适的模型。
查看Python版本和实现
1python3 --version
2Python 3.9.6
3
4python3 -c "import platform; print(platform.python_implementation())"
5CPython
Thread #
创建线程 #
在Python中,可以通过两种方式创建线程:
- 使用
threading.Thread
类的target
参数,直接传入要执行的函数。 - 创建一个继承自
threading.Thread
的自定义类,并重写run()
方法。
方式1:使用threading.Thread
直接传入目标函数
这种方式最常见且简单,只需将目标函数传入Thread
的target
参数中。
1import threading
2import time
3
4# 定义线程函数
5def print_numbers(end):
6 for i in range(1, end):
7 print(f"Number: {i}")
8 time.sleep(1)
9
10
11# 创建线程并指定目标函数
12thread = threading.Thread(target=print_numbers, args=(6,))
13
14# 启动线程
15thread.start()
16
17# 等待线程完成
18thread.join()
19
20print("Thread finished.")
方式2:继承threading.Thread
类并重写run
方法
这种方式适用于更复杂的场景,可以在自定义线程类中封装线程逻辑,便于扩展。
1import threading
2import time
3
4# 创建自定义线程类
5class PrintNumbersThread(threading.Thread):
6
7 def __init__(self, end):
8 super().__init__()
9 self._end = end
10
11 def run(self):
12 for i in range(1, self._end):
13 print(f"Number: {i}")
14 time.sleep(1)
15
16
17# 创建自定义线程类的实例
18thread = PrintNumbersThread(end=6)
19
20# 启动线程
21thread.start()
22
23# 等待线程完成
24thread.join()
25
26print("Thread finished.")
线程的问题 #
共享内存 #
同步方案:可以使用with
语句和 threading.Lock
一起使用,以简化加锁和解锁的操作。with lock
会自动在代码块开始时加锁,并在代码块结束时自动释放锁,避免手动调用 lock.acquire()
和 lock.release()
,从而让代码更简洁且避免忘记释放锁的错误。
使用队列进行线程间安全通信:queue.Queue
是一个线程安全的队列,适合用于线程间的安全通信。queue.Queue
内置了锁机制,多个线程可以安全地向队列写入数据,同时另一个线程可以安全地从队列读取数据,避免了自己管理锁的麻烦。适合的场景是生产者-消费者模型,其中一个或多个生产者线程将数据放入队列,而一个或多个消费者线程从队列取出数据并进行处理。
全局解释器锁(GIL) #
Python的全局解释器锁(GIL)确实限制了在默认的CPython中的多线程并行计算性能,特别是在计算密集型任务中。
GIL的优缺点:
优点:
- 简化了内存管理:由于Python内部的引用计数机制和GIL的存在,管理内存和进行垃圾回收的复杂度降低了。
- 提高了线程安全性:GIL通过限制并发执行,减少了多线程数据竞争和死锁的风险。
缺点: 限制了多线程的CPU并行计算能力:对于计算密集型任务,多个线程共享同一个 GIL,导致 CPU 无法充分利用多核架构。
线程开销 #
每个线程都占用一定量的内存(在Python进程和操作系统内核中)来记录该线程的状态。线程之间的切换也使用少量CPU时间。
通过复用用线程来执行多个任务,这些成本可以被摊薄到更多的任务中。Python提供了ThreadPool功能来处理这个问题。