Python的多线程模块主要是threading它提供了丰富的接口来创建和管理线程。线程基本知识点1. 导入**threading**模块首先你需要导入Python的threading模块。importthreading2. 定义线程任务你需要定义一个函数这个函数将作为线程执行的任务。这个函数可以包含任何代码但通常应该避免执行阻塞操作如I/O操作因为这会阻塞整个线程。defthread_task():print(Thread is running)# 这里可以添加更多的代码3. 创建线程使用threading.Thread类创建一个线程对象并将目标函数作为参数传递。threadthreading.Thread(targetthread_task)4. 启动线程start()使用start()方法启动线程。start()方法的一些关键点和用途启动线程执行start()方法会调用线程对象的目标函数即你在创建Thread对象时通过target参数指定的函数。这个目标函数将在新的线程中异步执行这意味着主线程不会等待这个函数完成而是继续执行后续的代码。线程调度在多线程编程中操作系统负责调度线程的执行。start()方法告诉操作系统开始调度这个线程。线程的执行顺序和时间是由操作系统的调度器决定的而不是由 Python 程序直接控制。只能调用一次一个线程的start()方法只能被调用一次。如果一个线程已经启动再次调用start()方法将会引发RuntimeError。一旦线程执行完毕它就不能被重新启动。如果你需要重新执行相同的任务你必须创建一个新的线程对象。thread.start()演示了start()方法的使用importthreadingimporttimedefthread_function(name):print(fThread{name}: starting)time.sleep(1)# 模拟耗时操作print(fThread{name}: finishing)# 创建线程对象指定目标函数和参数threadthreading.Thread(targetthread_function,args(Example,))# 启动线程thread.start()print(Main thread: thread has been started)# 等待线程完成thread.join()print(Main thread: thread has finished)在这个例子中创建了一个线程对象指定了它要执行的目标函数thread_function和一个参数Example。调用start()方法后线程开始执行主线程打印一条消息表示线程已启动然后继续执行。线程执行完毕后主线程通过调用join()方法等待线程结束最后打印一条消息表示线程已结束。5. 等待线程完成join()使用join()方法等待线程完成。这可以确保主程序在子线程完成之前不会退出。join()方法的一些关键点和用途阻塞调用线程当你在主线程中对一个子线程调用join()时主线程会等待该子线程执行完成之后才继续执行。这意味着join()方法可以用来同步线程确保某些线程在主程序继续执行之前已经完成它们的任务。确保资源正确释放在多线程程序中如果主线程结束而子线程还在运行那么程序可能不会正常退出因为子线程可能还在使用一些资源。通过使用join()方法你可以确保在程序退出之前所有子线程都已经完成这样可以正确地释放资源。等待线程完成如果你的程序中有多个线程在执行不同的任务你可能需要等待所有任务都完成后再进行下一步操作。在这种情况下你可以在每个线程上调用join()方法以确保所有线程都执行完毕。thread.join()演示了join()方法的使用importthreadingimporttimedefthread_function(name):print(fThread{name}: starting)time.sleep(2)print(fThread{name}: finishing)# 创建线程thread1threading.Thread(targetthread_function,args(One,))thread2threading.Thread(targetthread_function,args(Two,))# 启动线程thread1.start()thread2.start()# 在主线程中等待子线程完成thread1.join()thread2.join()print(Main thread: both threads have finished)在这个例子中我们创建了两个线程每个线程都会打印一条开始消息然后休眠2秒钟最后打印一条结束消息。在主线程中我们调用了每个线程的join()方法这样主线程会等待这两个线程都执行完毕后才打印最后的结束消息。6. 处理多个线程如果你需要同时运行多个线程可以创建多个线程对象并分别启动它们。defthread_task_1():print(Thread 1 is running)defthread_task_2():print(Thread 2 is running)thread1threading.Thread(targetthread_task_1)thread2threading.Thread(targetthread_task_2)thread1.start()thread2.start()thread1.join()thread2.join()7. 线程同步Lock在多线程环境中可能会遇到竞态条件即多个线程同时访问共享资源。为了避免这种情况可以使用锁Locks。Lock是一个同步原语可以用来防止多个线程同时执行某段代码从而避免共享资源的冲突。当一个线程获取锁定一个锁时其他尝试获取同一个锁的线程将被阻塞直到锁被释放。创建一个Lock对象lockthreading.Lock()在需要同步的代码块之前获取锁定Locklock.acquire()在代码块执行完毕后释放解锁Locklock.release()使用Lock的上下文管理器Python 的Lock对象提供了一个上下文管理器使得获取和释放锁更加方便和安全。使用with语句可以自动管理锁的获取和释放即使在发生异常时也能确保锁被释放。withlock:# 需要同步的代码块pass# 这里放置实际的代码Lock的使用示例importthreading# 共享资源shared_resource0# 锁对象lockthreading.Lock()defincrement():globalshared_resourcewithlock:# 使用上下文管理器自动获取和释放锁local_copyshared_resource local_copy1shared_resourcelocal_copy# 创建线程threads[]foriinrange(10):threadthreading.Thread(targetincrement)threads.append(thread)thread.start()# 等待所有线程完成forthreadinthreads:thread.join()print(fShared resource value:{shared_resource})在这个例子中我们创建了一个共享资源shared_resource和一个Lock对象。每个线程都会尝试修改这个共享资源。通过使用with lock:语句我们确保在同一时刻只有一个线程能够修改shared_resource从而避免了数据竞争。8. 线程安全的数据结构Queuethreading模块还提供了一些线程安全的数据结构如Queue用于线程间的数据传递。Queue类在queue模块中提供了一个 FIFO先进先出的数据结构允许多个生产者线程往队列中添加项目以及多个消费者线程从队列中取出项目。Queue类内部已经处理了所有必要的锁定因此是线程安全的。如何使用Queue以下是使用Queue的基本步骤创建一个Queue对象fromqueueimportQueue qQueue()向队列中添加项目q.put(item)从队列中获取项目itemq.get()等待队列非空后再获取项目itemq.get(blockTrue,timeoutNone)# 可以设置超时时间任务完成后标记队列中的项目已处理q.task_done()Queue的方法put(item)将一个项目放入队列。如果队列已满如果设置了最大大小此方法会阻塞直到队列中有空位。get(blockTrue, timeoutNone)从队列中取出一个项目。如果队列为空且block参数为True则此方法会阻塞直到队列中有项目可用。timeout参数可以设置超时时间。task_done()告诉队列一个项目已经被处理。这通常在消费者线程中调用特别是在使用join()方法等待所有项目都被处理时。join()阻塞直到队列中的所有项目都被task_done()调用处理。Queue的使用示例下面是一个使用Queue的示例演示了生产者-消费者模型fromqueueimportQueueimportthreadingimporttimedefproducer(q):foriinrange(5):print(fProducing{i})q.put(i)time.sleep(1)defconsumer(q):whileTrue:itemq.get()print(fConsuming{item})ifitemisNone:# 一个特殊的停止信号breakq.task_done()# 创建队列qQueue()# 创建生产者线程producer_threadthreading.Thread(targetproducer,args(q,))producer_thread.start()# 创建消费者线程consumer_threadthreading.Thread(targetconsumer,args(q,))consumer_thread.start()# 等待生产者线程完成producer_thread.join()# 发送停止信号q.put(None)# 等待消费者线程完成consumer_thread.join()print(Done)在这个例子中生产者线程向队列中添加项目消费者线程从队列中取出并处理项目。当生产者线程完成生产后它通过向队列中添加一个特殊的None项目来通知消费者线程停止。9. 理解全局解释器锁GILPython的全局解释器锁GIL是一个机制它确保在任何时刻只有一个线程执行Python字节码。这意味着在CPythonPython的官方和最常用的实现中多线程可能不会带来性能上的提升特别是在CPU密集型任务中。然而对于I/O密集型任务多线程仍然是有用的。因此GIL 被引入来保护内存管理避免多个线程同时执行 Python 字节码从而避免数据竞争和内存不一致的问题。GIL 的影响线程安全GIL 确保了在任何时刻只有一个线程执行 Python 字节码这意味着不需要对 Python 对象的访问进行额外的同步因为不会有多个线程同时修改它们。性能限制由于 GIL 的存在即使在多核处理器上Python 的多线程程序也不能有效地利用多核资源来执行 CPU 密集型任务。这是因为 GIL 限制了真正的并行执行导致多线程程序在 CPU 密集型任务上的性能提升有限。I/O 密集型任务的优势对于 I/O 密集型任务GIL 的影响较小因为线程在等待 I/O 操作如文件读写、网络通信时会释放 GIL允许其他线程运行。GIL 与多线程编程multithreading函数尝试通过创建四个线程来并行计算一个列表的总和。然而由于 GIL 的存在这些线程并不能真正并行执行。当一个线程执行时其他线程必须等待。这意味着即使有多个线程它们也不能同时利用多核 CPU 来加速计算。importthreadingfromqueueimportQueueimportcopyimporttimedefjob(l,q):ressum(l)q.put(res)defmultithreading(l):qQueue()threads[]foriinrange(4):tthreading.Thread(targetjob,args(copy.copy(l),q),nameT%i%i)t.start()threads.append(t)[t.join()fortinthreads]total0for_inrange(4):totalq.get()print(total)defnormal(l):totalsum(l)print(total)if__name____main__:llist(range(1000000))s_ttime.time()normal(l*4)print(normal: ,time.time()-s_t)s_ttime.time()multithreading(l)print(multithreading: ,time.time()-s_t)分析代码normal函数计算列表l的四倍长度的总和这是一个 CPU 密集型任务。multithreading函数创建四个线程每个线程计算列表l的总和并将结果放入队列q中。主线程等待所有子线程完成后从队列中取出结果并计算总和。结果在单线程的normal函数中计算可能相对较慢因为它在单个线程中执行所有计算。在多线程的multithreading函数中由于 GIL 的限制多线程并不能显著提高性能尤其是在 CPU 密集型任务中。实际上由于线程创建和管理的开销多线程版本可能比单线程版本更慢。结论GIL 对 Python 的多线程编程有显著影响特别是在 CPU 密集型任务中。对于这类任务可以考虑使用多进程通过multiprocessing模块来实现真正的并行计算因为每个进程有自己的 Python 解释器和内存空间不受 GIL 的限制。对于 I/O 密集型任务多线程仍然是一个有效的并发编程模型。线程基本操作获取当前活动线程enumerate你可以使用threading.enumerate()函数来获取一个包含所有当前活动线程的列表。importthreading# 获取当前所有活动线程threadsthreading.enumerate()forthreadinthreads:print(thread.name)获取线程名称name每个线程都有一个名称可以通过name属性获取。my_threadthreading.Thread(targetmy_function,nameMyThread)print(my_thread.name)# 输出: MyThread获取线程的标识符ident每个线程都有一个唯一的标识符可以通过ident属性获取。my_threadthreading.Thread(targetmy_function)print(my_thread.ident)# 输出: 线程的唯一标识符一个长整数检查线程是否活着is_alive使用is_alive()方法检查线程是否还活着即是否还在执行。my_threadthreading.Thread(targetmy_function)my_thread.start()print(my_thread.is_alive())# 输出: True因为线程正在运行my_thread.join()print(my_thread.is_alive())# 输出: False因为线程已经完成设置守护线程daemon守护线程Daemon Thread是后台线程用于执行一些后台任务如垃圾回收、系统日志维护等。在Python中守护线程的生命周期是依附于主线程Main Thread的这意味着当主线程结束时守护线程也会被强制结束不管它是否完成了任务。守护线程的特点依附于主线程守护线程不会阻止程序退出。如果只剩下守护线程在运行程序将自动退出。用于后台任务守护线程通常用于执行一些不会阻塞程序结束的任务比如日志记录、状态监控等。自动结束当主线程结束时所有守护线程都会被终止无论它们是否完成了任务。守护线程的意义后台任务处理 守护线程通常用于执行一些后台任务比如日志记录、状态监控、资源清理、垃圾回收等。这些任务不需要用户直接干预但对系统的稳定运行至关重要。防止程序挂起 守护线程允许程序在完成主要任务后立即退出而不是无限期地等待后台任务完成。这对于命令行工具或者需要快速启动和退出的应用程序尤其重要。资源管理 在某些情况下程序可能需要释放资源如关闭文件描述符、网络连接等而这些操作可以在守护线程中执行确保即使主程序结束这些资源也能被妥善处理。提高效率 对于一些不需要即时反馈的任务使用守护线程可以提高程序的效率。主线程可以继续处理用户交互或其他重要任务而将耗时的后台任务交给守护线程处理。服务线程 守护线程可以作为服务线程为整个应用程序提供服务比如处理请求队列、监控网络状态等而不需要阻塞主线程。异常处理 如果主线程因为某些原因崩溃或异常退出守护线程可以负责最后的清理工作比如记录异常信息、释放资源等。平滑用户体验 对于图形用户界面GUI应用程序守护线程可以用来处理耗时的任务避免界面冻结从而提供更平滑的用户体验。调试和诊断 守护线程可以用来收集和记录程序运行时的信息这对于调试和诊断问题非常有用。在创建线程时或者在启动线程之前通过设置daemon属性为True来指定一个线程为守护线程。importthreadingimporttimedefdaemon_task():whileTrue:print(Daemon thread is running)time.sleep(2)# 创建线程对象daemon_threadthreading.Thread(targetdaemon_task)# 设置为守护线程daemon_thread.daemonTrue# 启动线程daemon_thread.start()# 主线程执行的代码print(Main thread is running)time.sleep(3)# 主线程睡眠3秒后结束在这个例子中主线程在睡眠3秒后结束而守护线程daemon_thread会在主线程结束时被自动终止。注意事项资源清理由于守护线程可能会在任何时候被终止所以不应该在守护线程中执行需要清理资源的操作比如关闭文件、释放锁等。不适合长时间任务守护线程不适合执行长时间运行的任务因为它们可能在任何时候被终止。调试难度由于守护线程的生命周期不确定调试守护线程中的问题可能会更加困难。守护线程与非守护线程非守护线程User Thread是默认的线程类型它们会一直运行直到结束。如果所有非守护线程都结束了只剩下守护线程程序会退出。非守护线程可以用来执行需要确保完成的任务比如文件下载、数据库操作等。获取主线程current_thread主线程程序启动时创建的线程可以通过current_thread()函数获取。importthreading main_threadthreading.current_thread()print(main_thread.name)# 输出:MainThread添加线程到列表如果你想要管理一个线程列表可以创建一个列表并在其中添加线程对象。创建一个空列表来存储线程对象。threads[]例子importthreadingimporttimedefthread_task():print(fThread{threading.current_thread().name}is running)time.sleep(1)# 模拟耗时操作# 创建线程列表threads[]# 创建并添加线程到列表foriinrange(5):# 创建5个线程tthreading.Thread(targetthread_task)t.start()# 启动线程threads.append(t)# 添加到线程列表# 等待所有线程完成fortinthreads:t.join()print(All threads have completed.)在这个示例中我们创建了5个线程每个线程都会执行thread_task函数。我们首先将每个创建的线程对象添加到threads列表中然后启动它们。最后我们遍历threads列表等待每个线程完成确保主程序在所有子线程完成后才继续执行。