Python多线程编程详解:Thread、GIL、锁与Event机制实战
本文深入解析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()
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









