多线程编程threading库

多线程编程

并行与并发

  1. 并发:指一段时间内交替执行任务。(任务数多于CPU核数时)
  2. 并行:一段时间任务同时进行(任务数小于CPU核数时)

进程与线程

threading 库是 Python 中用于多线程编程的标准库,允许创建和管理线程,从而实现并发执行任务。多线程适用于 I/O 密集型任务(如文件读写、网络请求等),但不适合 CPU 密集型任务(因为 Python 的全局解释器锁 GIL 会限制多线程的并行执行)。

基本概念

线程:线程是操作系统能够调度的最小单位,一个进程可以包含多个线程,共享进程的内存空间。

GIL(全局解释器锁):Python 的 GIL 会限制同一时间只有一个线程执行 Python 字节码,因此多线程在 CPU 密集型任务中无法实现真正的并行。

线程安全:多个线程同时访问共享资源时,可能会导致数据不一致问题,需要使用锁(Lock)等机制来保证线程安全。

基本使用方法

  1. Thread类是threading的核心
python 复制代码
# Thread参数
threading.Thread(
    target=None,  # 线程要执行的函数
    name=None,    # 线程的名称
    args=(),      # 传递给目标函数的参数(元组)
    kwargs={},    # 传递给目标函数的关键字参数(字典)
    daemon=None   # 是否设置为守护线程
)
  1. 常用方法
    • start():启动线程,调用线程的 run() 方法。
    • run():线程执行的主体方法,可以重写。
    • join(timeout=None):等待线程结束。timeout 是超时时间(秒),超时后继续执行主线程。
    • is_alive():返回线程是否仍在运行。
    • name:获取或设置线程的名称。
    • daemon:获取或设置线程是否为守护线程。
创建线程
  1. 直接传入目标函数
python 复制代码
import threading

def worker():
    print("Worker thread is running")

# 创建线程
thread = threading.Thread(target=worker)

# 启动线程
thread.start()

# 等待线程结束
thread.join()
print("Main thread finished")
  1. 继承Thread重写run()
python 复制代码
import threading

class MyThread(threading.Thread):
    def run(self):
        print("Worker thread is running")

# 创建线程
thread = MyThread()

# 启动线程
thread.start()

# 等待线程结束
thread.join()
print("Main thread finished")

线程的参数传递

  1. 如果需要向线程函数传递参数,可以使用 args 或 kwargs。
python 复制代码
import threading

def worker(name, delay):
    print(f"{name} is running")
    import time
    time.sleep(delay)
    print(f"{name} is done")

# 创建线程并传递参数
thread = threading.Thread(target=worker, args=("Thread-1", 2))

# 启动线程
thread.start()

# 等待线程结束
thread.join()
print("Main thread finished")

线程同步(防止资源竞争)

多个线程同时访问共享资源时,可能会导致数据不一致问题。可以使用锁(Lock)来保证线程安全。

  1. 没有使用线程输出正常
python 复制代码
a = 0
b = 10000000


def sum1():
    for i in range(b):
        global a
        a += 1
    print(a)


def sum2():
    for i in range(b):
        global a
        a += 1
    print(a)


if __name__ == '__main__':
    sum1()  # 输出 10000000
    sum2()  # 输出 20000000
  1. 使用线程
python 复制代码
from threading import Thread

a = 0
b = 10000000


def sum1():
    for i in range(b):
        global a
        a += 1
    print(a)


def sum2():
    for i in range(b):
        global a
        a += 1
    print(a)


if __name__ == '__main__':
    t1 = Thread(target=sum1)
    t2 = Thread(target=sum2)
    t1.start()  # 输出 12371445
    t2.start()  # 输出 13385086

这里的输出结果明显不一样了,用多线程对全局变量 a 进行累加操作,最终输出的结果并不是预期的 20000000,这是由于多线程环境下的竞态条件(Race Condition) 所导致的。

  • 在 Python 中,a += 1 这一操作并不是原子操作。原子操作是指在执行过程中不会被其他线程中断的操作。而 a += 1 实际上包含了三个步骤:
    • 读取:从内存中读取 a 的当前值。
    • 计算:将读取的值加 1。
    • 写入:将计算结果写回到内存中。
    • 当两个线程同时对 a 进行操作时,可能会出现以下情况:
    • 线程 t1 读取了 a 的值,假设为 x。
    • 线程 t2 也读取了 a 的值,由于线程 t1 还没有来得及将计算结果写回内存,所以线程 t2 读取到的值也是 x。
    • 线程 t1 将 x 加 1 得到 x + 1,并将其写回到内存中。
    • 线程 t2 同样将 x 加 1 得到 x + 1,并将其写回到内存中。
      这样,虽然两个线程都执行了一次累加操作,但实际上 a 的值只增加了 1,而不是 2。这种情况会多次发生,导致最终结果小于预期值。
  1. 解决资源竞争
  • 加锁(Lock)来保证线程的安全
python 复制代码
from threading import Thread, Lock

a = 0
b = 10000000
lock = Lock()


def sum1():
    lock.acquire()
    for i in range(b):
        global a
        a += 1
    print(a)
    lock.release()


def sum2():
    lock.acquire()
    for i in range(b):
        global a
        a += 1
    print(a)
    lock.release()


if __name__ == '__main__':
    t1 = Thread(target=sum1)
    t2 = Thread(target=sum2)
    t1.start()  # 输出 10000000
    t2.start()  # 输出 20000000
  • join()等待线程结束
python 复制代码
from threading import Thread
a = 0
b = 10000000


def sum1():
    for i in range(b):
        global a
        a += 1
    print(a)


def sum2():
    for i in range(b):
        global a
        a += 1
    print(a)


if __name__ == '__main__':
    t1 = Thread(target=sum1)
    t2 = Thread(target=sum2)
    t1.start()  # 输出 10000000
    t1.join()
    t2.start()  # 输出 20000000
  • threading.Lock 类
    • Lock 类用于实现线程同步,确保多个线程不会同时访问共享资源。
    • 常用方法
      acquire(blocking=True, timeout=-1):获取锁。(blocking:如果为 True,线程会阻塞直到获取锁;如果为 False,线程会立即返回是否成功获取锁。timeout:阻塞的最长时间(秒),超时后返回 False。)
      release():释放锁。
      locked():返回锁是否被占用。
  • threading.RLock 类
    • RLock(可重入锁)是 Lock 的增强版,允许同一个线程多次获取锁。
      常用方法
      与 Lock 相同,但支持重入。

守护线程

守护线程(Daemon Thread)是一种在后台运行的线程,当主线程退出时,守护线程会自动退出。

python 复制代码
import threading
import time

def daemon_worker():
    print("Daemon thread started")
    time.sleep(2)
    print("Daemon thread finished")  # 可能不会执行,因为主线程退出了

# 创建守护线程
thread = threading.Thread(target=daemon_worker)
thread.daemon = True  # 设置为守护线程

# 启动线程
thread.start()

# 主线程退出
print("Main thread finished")
  • 输出
python 复制代码
# 正常输出应该是
Daemon thread started
Daemon thread finished
Main thread finished
# 实际输出为
Daemon thread started
Main thread finished

原因是因为守护线程中有一个 time.sleep(2)函数,当代码运行时,主线先启动线程 thread.start()

再执行 print("Main thread finished"),再运行 thread.start()中由于有两秒睡眠时间,但是主线程并不会等待守护线程运行再运行,他们之间是并行的关系,主线程结束时守护线程还没有睡眠结束自然不会执行 print("Daemon thread finished")如果删去time.sleep(2)函数则会正常输出。

  • 常用方法

    threading.Thread(target, args, kwargs):创建线程。

    thread.start():启动线程。

    thread.join():等待线程结束。

    threading.Lock():创建锁,用于线程同步。

    threading.current_thread():获取当前线程对象。

    threading.active_count():获取当前活动的线程数。

相关推荐
老胖闲聊7 小时前
Python Copilot【代码辅助工具】 简介
开发语言·python·copilot
Blossom.1187 小时前
使用Python和Scikit-Learn实现机器学习模型调优
开发语言·人工智能·python·深度学习·目标检测·机器学习·scikit-learn
曹勖之7 小时前
基于ROS2,撰写python脚本,根据给定的舵-桨动力学模型实现动力学更新
开发语言·python·机器人·ros2
lyaihao8 小时前
使用python实现奔跑的线条效果
python·绘图
ai大师9 小时前
(附代码及图示)Multi-Query 多查询策略详解
python·langchain·中转api·apikey·中转apikey·免费apikey·claude4
小小爬虾9 小时前
关于datetime获取时间的问题
python
蓝婷儿10 小时前
6个月Python学习计划 Day 16 - 面向对象编程(OOP)基础
开发语言·python·学习
chao_78911 小时前
链表题解——两两交换链表中的节点【LeetCode】
数据结构·python·leetcode·链表
大霞上仙11 小时前
nonlocal 与global关键字
开发语言·python
Mark_Aussie12 小时前
Flask-SQLAlchemy使用小结
python·flask