文章目录
1、线程基本概念
1.1、基本概念
1、线程是操作系统能够进行运算调度的最小单位。
2、线程被包含在进程之中,是进程中的实际运作单位。
3、一个进程可以包含多个线程,这些线程共享进程的内存空间。
每个线程都有他自己的一组CPU寄存器,称为线程的上下文,该上下文反映了线程上次运行该线程的CPU寄存器的状态。
指令指针和堆栈指针寄存器是线程上下文中两个最重要的寄存器,线程总是在进程得到上下文中运行的,这些地址都用于标志拥有线程的进程地址空间中的内存。
Python的线程虽然是真正的线程,但解释器执行代码时,有一个GIL锁:Global Interpreter Lock,任何Python线程执行前,必须先获得GIL锁,然后,每执行100条计步(ticks)(也可以认为是虚拟机指令或字节码),解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。
Python的线程虽然是真正的线程,但解释器执行代码时,有一个GIL锁:Global Interpreter Lock,任何Python线程执行前,必须先获得GIL锁,然后,每执行100条计步(ticks)(也可以认为是虚拟机指令或字节码),解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。
GIL是Python解释器设计的历史遗留问题,通常我们用的解释器是官方实现的CPython,要真正利用多核,除非重写一个不带GIL的解释器。所以,在Python中,可以使用多线程,但不要指望能有效利用多核。如果一定要通过多线程利用多核,那只能通过C扩展来实现,不过这样就失去了Python简单易用的特点。不过Python虽然不能利用多线程实现多核任务,但可以通过多进程实现多核任务。多个Python进程有各自独立的GIL锁,互不影响。
1.1、GIL(全局解释器锁)
GIL 是什么:GIL 是 CPython 解释器中的一个互斥锁,它规定在任一时刻,只有一个线程可以执行 Python 字节码。
对 CPU 密集型任务的影响:对于计算密集型任务(如数学计算、图像处理),GIL 会使得多线程无法利用多核 CPU 实现真正的并行,性能甚至可能不如单线程。
对 I/O 密集型任务的影响:对于 I/O 密集型任务(如网络请求、文件读写),线程在等待 I/O 时会释放 GIL,其他线程可以继续执行。因此,多线程能有效提升这类任务的效率。
2、python中的线程模块
Python 线程模块
3、创建线程的两种方式
线程是无序的,启动了线程A,然后紧接着启动了线程B。不能认为线程A一定会先于线程B开始执行它的代码。操作系统调度器会根据当前的系统负载、CPU核心的忙闲状态、线程优先级等因素来决定下一个时间片分配给哪个线程。
3.1、使用库函数创建线程
以下代码使用库函数创建线程,其中
t1 = threading.Thread(target=print_numbers) 语句在创建线程类的对象,并且设置线程执行的回调函数。
t1.start()为开启线程,即线程开始运行。start几乎是瞬间,不消耗时间的。
t1.join()函数则是等待线程运行结束再运行主程序,在join函数启动时,主线程将一直阻塞直到子线程结束才运行,但是以下程序运行了主线程、线程t1、线程t2。线程t2不会被jion函数阻塞,正如上述文字所言,jion函数只阻塞主线程。
import threading
import time
def print_numbers():
for i in range(5):
print(f"数字: {i}")
time.sleep(1)
def print_letters():
for letter in 'ABCDE':
print(f"字母: {letter}")
time.sleep(1)
# 创建线程
t1 = threading.Thread(target=print_numbers)
t2 = threading.Thread(target=print_letters)
# 启动线程
t1.start()
t2.start()
# 等待线程结束
t1.join()
t2.join()
print("所有线程执行完毕")
3.2、继承 Thread 类
以下代码是通过基础Thread类,实现自己的Thread类,启动线程的方法和流程与3.1中是一致的。
import threading
import time
class MyThread(threading.Thread):
def __init__(self, name, delay):
super().__init__()
self.name = name
self.delay = delay
def run(self):
print(f"线程 {self.name} 开始")
for i in range(3):
print(f"{self.name}: {i}")
time.sleep(self.delay)
print(f"线程 {self.name} 结束")
# 使用自定义线程类
thread1 = MyThread("Thread-1", 1)
thread2 = MyThread("Thread-2", 0.5)
thread1.start()
thread2.start()
thread1.join()
thread2.join()
4、线程基本操作
4.1、线程状态检查
线程状态检查通过类中的方法is_alive判断线程是否还在运行。返回BOOL类型的TURE、FALSE。
import threading
import time
def worker():
print("线程开始工作")
time.sleep(2)
print("线程工作完成")
t = threading.Thread(target=worker)
print(f"线程启动前: is_alive={t.is_alive()}")
t.start()
print(f"线程启动后: is_alive={t.is_alive()}")
t.join()
print(f"线程结束后: is_alive={t.is_alive()}")
4.2、守护线程
关键设置:
daemon_thread = threading.Thread(target=daemon_worker)
daemon_thread.daemon = True # 关键设置!
守护线程:随着主线程结束而强制终止。
普通线程:主线程会等待其完成。
import threading
import time
def daemon_worker():
print("守护线程开始")
for i in range(5):
print(f"守护线程运行中... {i}")
time.sleep(1)
print("守护线程结束") # 可能不会执行到这一行
def normal_worker():
print("普通线程开始")
time.sleep(2)
print("普通线程结束")
# 创建守护线程
daemon_thread = threading.Thread(target=daemon_worker)
daemon_thread.daemon = True # 设置为守护线程
# 创建普通线程
normal_thread = threading.Thread(target=normal_worker)
daemon_thread.start()
normal_thread.start()
# 只等待普通线程
normal_thread.join()
print("主程序结束")
5、线程同步 - 锁
线程锁:
with self.lock:
# 临界区代码
# 同一时间只有一个线程能执行这里的代码
Lock 确保互斥访问
with 语句自动处理锁的获取和释放
以下代码的输出:
安全计数器结果: 500
不安全计数器结果: 487 # 或其他小于500的数字
import threading
import time
class SharedCounter:
def __init__(self):
self.value = 0
self.lock = threading.Lock()
def increment(self):
with self.lock: # 自动获取和释放锁
current = self.value
time.sleep(0.001) # 模拟一些处理时间
self.value = current + 1
def worker(counter, iterations):
for _ in range(iterations):
counter.increment()
# 测试不加锁的情况
class UnsafeCounter:
def __init__(self):
self.value = 0
def increment(self):
current = self.value
time.sleep(0.001)
self.value = current + 1
# 比较安全和不安全的计数器
safe_counter = SharedCounter()
unsafe_counter = UnsafeCounter()
threads = []
# 安全计数
for _ in range(5):
t = threading.Thread(target=worker, args=(safe_counter, 100))
threads.append(t)
t.start()
for t in threads:
t.join()
threads.clear()
# 不安全计数
for _ in range(5):
t = threading.Thread(target=worker, args=(unsafe_counter, 100))
threads.append(t)
t.start()
for t in threads:
t.join()
print(f"安全计数器结果: {safe_counter.value}") # 应该是 500
print(f"不安全计数器结果: {unsafe_counter.value}") # 可能小于 500
6、线程间通信
6.1、使用队列 (Queue)
关键技术点:
1、队列的线程安全
q.put(item) # 自动加锁,线程安全
item = q.get() # 自动加锁,线程安全
2、任务完成跟踪
q.task_done() # 消费者调用,标记一个任务完成
q.join() # 主线程等待所有任务完成
3、消费者退出机制
item = q.get(timeout=3) # 超时机制避免无限等待
except queue.Empty: # 捕获空队列异常
break # 优雅退出
import threading
import queue
import time
import random
def producer(q, producer_id):
for i in range(3):
item = f"产品-{producer_id}-{i}"
q.put(item)
print(f"生产者 {producer_id} 生产了: {item}")
time.sleep(random.random())
print(f"生产者 {producer_id} 完成")
def consumer(q, consumer_id):
while True:
try:
item = q.get(timeout=3) # 3秒超时
print(f"消费者 {consumer_id} 消费了: {item}")
q.task_done()
except queue.Empty:
print(f"消费者 {consumer_id} 超时退出")
break
# 创建队列
q = queue.Queue()
# 创建生产者和消费者
producers = []
consumers = []
# 启动生产者
for i in range(2):
p = threading.Thread(target=producer, args=(q, i))
producers.append(p)
p.start()
# 启动消费者
for i in range(2):
c = threading.Thread(target=consumer, args=(q, i))
consumers.append(c)
c.start()
# 等待生产者完成
for p in producers:
p.join()
# 等待队列清空
q.join()
# 消费者会自动退出
for c in consumers:
c.join()
print("所有任务完成")
6.2、信号量 (Semaphore)
关键机制解析:
1、信号量工作原理
self.semaphore = threading.Semaphore(3) # 初始值为3
信号量:维护一个计数器,表示可用资源数量
acquire():计数器-1,如果为0则阻塞
release():计数器+1,唤醒等待线程
2、 with语句的自动管理
with self.semaphore:
# 自动调用 semaphore.acquire()
# 使用资源...
# 自动调用 semaphore.release()
import threading
import time
import random
# 模拟有限资源(如数据库连接池)
class ConnectionPool:
def __init__(self, size):
self.semaphore = threading.Semaphore(size)
def get_connection(self, thread_id):
with self.semaphore:
print(f"线程 {thread_id} 获取连接")
time.sleep(random.uniform(1, 3)) # 模拟使用连接
print(f"线程 {thread_id} 释放连接")
def worker(pool, thread_id):
print(f"线程 {thread_id} 请求连接")
pool.get_connection(thread_id)
# 创建连接池(只有3个连接)
pool = ConnectionPool(3)
threads = []
for i in range(10): # 创建10个线程,但只有3个能同时获取连接
t = threading.Thread(target=worker, args=(pool, i))
threads.append(t)
t.start()
time.sleep(0.2) # 稍微错开启动时间
for t in threads:
t.join()
7、定时器线程
import threading
import time
def delayed_task(message):
print(f"定时任务执行: {message}")
def periodic_task():
print("周期任务执行")
# 重新设置定时器,实现周期性执行
threading.Timer(2, periodic_task).start()
# 一次性定时任务
print("启动定时任务")
timer1 = threading.Timer(3, delayed_task, args=("3秒后执行",))
timer1.start()
# 周期性任务
print("启动周期任务")
timer2 = threading.Timer(1, periodic_task)
timer2.start()
# 等待一段时间
time.sleep(10)
print("主程序结束")
# 取消定时器(如果需要)
# timer1.cancel()
# timer2.cancel()
658

被折叠的 条评论
为什么被折叠?



