python---线程

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()

推荐文章

Python学习系列之多线程

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值