Python多线程编程详解:Thread、GIL、锁与Event机制实战

小鹿同学
小鹿同学
发布时间:2026-10-13| 5人浏览过
原创

本文深入解析Python多线程编程核心机制。详细阐述Thread类的两种创建方式及其常用属性方法,解释CPython中GIL对多线程并发的影响。通过代码示例演示互斥锁Lock与递归锁RLock的使用场景,以及利用Event对象实现线程间同步通信的具体步骤,帮助开发者掌握并发编程的关键技术。

线程与进程基础概念

首先,线程共享其所属进程的地址空间,而每个进程拥有独立的地址空间。

其次,线程可以访问进程内的所有数据,且线程之间可以相互访问彼此的数据。

第三,尽管共享地址空间,但线程之间的局部数据是独立的。

第四,子进程在创建时会复制父进程的数据空间。

第五,子进程启动后成为独立实体,父进程只能终止子进程,无法直接与子进程进行数据交换。

第六,修改线程中的数据会影响其他所有线程;而对于进程,对父进程的修改不会影响已存在的子进程。

threading.Thread类详解

Thread是threading模块中最核心的类,用于创建线程。创建线程主要有两种方式:一是继承Thread类并重写run方法;二是实例化threading.Thread对象,并在初始化函数__init__中将可调用对象作为参数传入。以下示例展示如何通过继承Thread类来创建线程:

import threading
import time

class MyThread(threading.Thread):
 def __init__(self, arg):
  # super(MyThread, self).__init__() # 新式类继承原有方法写法
  threading.Thread.__init__(self)
  self.arg = arg

 def run(self):
  time.sleep(2)
  print(self.arg)

for i in range(10):
 thread = MyThread(i)
 print(thread.name)
 thread.start()

另一种创建线程的方法示例如下:

import threading
import time

def process(arg):
 time.sleep(2)
 print(arg)

for i in range(10):
 t = threading.Thread(target=process, args=(i,))
 print(t.name)
 t.start()

Thread类还提供了以下常用方法与属性供开发者使用:

Thread.getName():用于获取当前线程的名称。

Thread.setName():用于设置线程的名称。

Thread.name:直接访问或修改线程名称的属性。

Thread.ident:获取线程的唯一标识符。该标识符为非零整数,仅在调用start()方法后有效,否则返回None。

判断线程是否处于激活状态。从调用start()方法启动线程开始,直到run()方法执行完毕或因未处理异常而中断,这段时间内线程被视为激活状态。

Thread.is_alive()(Python 3)或Thread.isAlive()(Python 2):用于检查线程是否存活。

Thread.join([timeout]):调用此方法会使主调线程阻塞,直到被调用线程执行结束或超时。timeout参数为数值类型,表示最大等待时间;若未提供该参数,主调线程将无限期阻塞直到被调线程结束。

Python GIL(全局解释器锁)机制

GIL并非Python语言本身的特性,而是CPython解释器实现时引入的概念。类比C++语言标准与不同编译器(如GCC、Intel C++、Visual C++)的关系,Python也有多种执行环境,如CPython、PyPy、Psyco等,其中JPython就没有GIL。由于CPython是默认执行环境,许多人误将GIL归咎于Python语言缺陷。需明确:GIL是CPython的实现细节,Python语言本身可以不依赖GIL。

线程锁的基本使用示例:

# 锁:GIL 全局解释器 它是为了保证线程在运行过程中不被抢占
number = 0
lock = threading.RLock() # 创建锁


def run(num):
 lock.acquire() # 加锁
 global number
 number += 1
 print(number)
 time.sleep(2)
 lock.release() # 释放锁

for i in range(10):
 t = threading.Thread(target=run, args=(i, ))
 t.start()

Join方法与Daemon守护线程设置

在主线程A中创建子线程B,并调用B.setDaemon(True),表示将主线程A设置为守护线程。此时,若主线程A执行结束,无论子线程B是否完成,主线程与子线程B将一同退出。setDaemon方法的作用与join相反。需注意:必须在调用start()方法之前设置守护属性,否则程序可能因等待子线程而无限挂起。

class MyThread1(threading.Thread):
 def __init__(self):
  threading.Thread.__init__(self)

 def run(self):
  print("thread start")
  time.sleep(3)
  print('thread end')

print('main start')
thread1 = MyThread1()
# thread1.setDaemon(True)  # 设置子线程是否跟随主线程一起结束
thread1.start()
time.sleep(1)
print('satrt join')
# thread1.join() # 使主线程阻塞,直至子线程运行完毕再继续主线程
print('end join')
def run(n):
 print('[%s]------running----\n' % n)
 time.sleep(2)
 print('--done--')


def main():
 for i in range(5):
  t = threading.Thread(target=run, args=[i,])
  t.start()
  # t.join()
  print('starting thread', t.getName())


m = threading.Thread(target=main,args=[])
# m.setDaemon(True) # 将主线程设置为Daemon线程,它退出时,其它子线程会同时退出,不管是否执行完任务
m.start()
# m.join() # 使主线程阻塞,直至子线程运行完毕再继续主线程
print("---main thread done----")

线程锁(互斥锁Mutex)应用

一个进程可启动多个线程,这些线程共享父进程的内存空间,即每个线程都能访问同一份数据。当两个或多个线程同时修改同一份数据时,可能会引发数据竞争问题。

num = 100 # 设定一个共享变量
def subNum():
 global num # 在每个线程中都获取这个全局变量
 print('--get num:', num)
 time.sleep(2)
 num -= 1 # 对此公共变量进行-1操作
thread_list = []
for i in range(100):
 t = threading.Thread(target=subNum)
 t.start()
 thread_list.append(t)
for t in thread_list: # 等待所有线程执行完毕
 t.join()
print('final num:', num)
# 加锁版本
def subNum():
 global num # 在每个线程中都获取这个全局变量
 print('--get num:', num)
 time.sleep(1)
 lock.acquire() # 修改数据前加锁
 num -= 1 # 对此公共变量进行-1操作
 lock.release() # 修改后释放

num = 100 # 设定一个共享变量
thread_list = []
lock = threading.Lock() # 生成全局锁
for i in range(100):
 t = threading.Thread(target=subNum)
 t.start()
 thread_list.append(t)
for t in thread_list: # 等待所有线程执行完毕
 t.join()
print('final num:', num)

RLock与Lock的区别分析

RLock允许同一线程多次调用acquire,而Lock不允许这种情况,否则会导致死锁,程序无法确定解开哪把锁。注意:使用RLock时,acquire和release必须成对出现,即调用n次acquire,必须相应调用n次release才能真正释放锁资源。

Events事件对象机制

Python提供了Event对象用于线程间通信,它是一种由线程设置的信号标志。如果信号标志为真,其他线程将等待直到信号被清除。Event对象实现了简单的线程通信机制,提供了设置信号、清除信号和等待等功能,用于实现线程间的同步通信。

event = threading.Event():用于创建一个Event对象实例。

1. 设置信号操作 event.set()

使用Event对象的set()方法可将内部信号标志设为真。Event对象提供了isSet()方法来判断内部信号标志的状态。调用set()后,isSet()方法将返回真。

2. 清除信号操作 event.clear()

使用Event对象的clear()方法可将内部信号标志设为假。调用clear()后,isSet()方法将返回假。

3. 等待信号操作 event.wait()

Event对象的wait()方法仅在内部信号为真时快速执行并返回。当内部信号标志为假时,wait()方法会一直阻塞等待,直到信号变为真。因此,必须通过set()将信号标志设为真,wait()才能继续执行。

def do(event):
 print('start')
 event.wait()
 print('execute')

event_obj = threading.Event()
for i in range(10):
 t = threading.Thread(target=do, args=(event_obj,))
 t.start()

event_obj.clear()
inp = input('输入内容:')
if inp == 'true':
 event_obj.set()

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。