多线程编程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():获取当前活动的线程数。

相关推荐
奔跑吧邓邓子20 分钟前
【Python爬虫(12)】正则表达式:Python爬虫的进阶利刃
爬虫·python·正则表达式·进阶·高级
码界筑梦坊44 分钟前
基于Flask的京东商品信息可视化分析系统的设计与实现
大数据·python·信息可视化·flask·毕业设计
pianmian11 小时前
python绘图之箱型图
python·信息可视化·数据分析
csbDD1 小时前
2025年网络安全(黑客技术)三个月自学手册
linux·网络·python·安全·web安全
赔罪3 小时前
Python 高级特性-切片
开发语言·python
伊一大数据&人工智能学习日志3 小时前
selenium爬取苏宁易购平台某产品的评论
爬虫·python·selenium·测试工具·网络爬虫
说是用户昵称已存在3 小时前
Pycharm+CodeGPT+Ollama+Deepseek
ide·python·ai·pycharm
Fansv5874 小时前
深度学习-2.机械学习基础
人工智能·经验分享·python·深度学习·算法·机器学习
wang_yb4 小时前
『Python底层原理』--Python对象系统探秘
python·databook
databook4 小时前
『Python底层原理』--Python对象系统探秘
后端·python