Python 多线程

0x01 线程相关概念

1.1 进程

一个进程就是一个正在执行的程序,每一个进程都有自己独立的一块内存空间、一组系统资源。在进程的概念中,每一个进程的内部数据和状态都是完全独立的。在 Windows 操作系统中,一个进程就是一个 exe 或者 dll 程序,它们相互独立,相互也可以通信。

1.2 线程

在一个进程中可以包含多个线程(进程是线程的一个容器),多个线程共享一块内存空间和一组系统资源。所以,系统在各个线程之间切换时,开销要比进程小得多,正因如此,线程被称为轻量级进程。

1.3 主线程

Python 程序至少有一个线程,这就是主线程,程序在启动后由 Python 解释器负责创建主线程,在程序结束后由 Python 解释器负责停止主线程。在多线程中,主线程负责其他线程的启动、挂起、停止等操作。其他线程被称为子线程。

0x02 线程模块-threading

Python 官方提供的 threading 模块可以进行多线程编程。threading 模块提供了多线程编程的高级 API,使用起来比较简单。在 threading 模块中提供了线程类 Thread,还提供了很多线程相关的函数,这些函数中常用的如下:

  • active_count():返回当前处于活动状态的线程个数
  • current_thread():返回当前的Thread对象
  • main_thread():返回主线程对象。主线程是Python解释器启动的线程
# coding=utf-8
import threading
# 获取当前线程对象
t = threading.current_thread()
# 输出当前线程的名字
print(t.name)
# 获取当前活动线程数量
print(threading.active_count())
# 获取主线程
main = threading.main_thread()
print(main.name)
#输出结果为:
MainThread
1
MainThread

2.1 创建子线程

创建一个可执行的子线程,需要如下两个要素:

  • 线程对象:线程对象是 threading 模块的线程类 Thread 或 Thread 子类所创建的对象。
  • 线程体:线程体是子线程要执行的代码,这些代码会被封装到一个函数中。子线程在启动后会执行线程体。

实现线程体主要有以下两种方式:

  • 自定义函数实现线程体
  • 自定义线程类实现线程体
2.1.1 自定义函数的方式

Thread(target=None,nmae=None,args=())

  • target 参数指向线程体函数,我们可以自定义该线程体函数
  • 通过 name 参数可以设置线程名,如果省略这个参数,则系统会为其分配一个名称
  • args 是为线程体函数提供的参数,是一个元组类型
# coding=utf-8
import threading
import time

# 创建一个线程体函数
def thread_body(name):
    # 获取当前线程对象
    t = threading.current_thread()
    # 给个循环
    for n in range(5):
        # 打印线程的名字和传入的参数
        print('线程{}正在执行,参数为{}。。。。\n'.format(t.name, name))
        # 睡两秒
        time.sleep(2)
    print("线程{}执行完毕!\n".format(t.name))

# 准备两个线程
t1 = threading.Thread(target=thread_body, args=('参数1',), name='线程01')
t2 = threading.Thread(target=thread_body, args=('参数2',), name='线程02')

# 启动线程
t1.start()
t2.start()

# 等待所有线程完成
t1.join()
t2.join()

print("所有线程执行完毕!")

从运行结果可见,两个子线程是交错运行的。在多线程编程时,要注意每个子线程执行的机会,主要是通过让子线程休眠来让当前线程暂停执行,其他线程才有机会执行。如果子线程没有休眠,则基本上在第1哥子线程执行完毕后,再执行第2个子线程。

2.1.2 继承 Thread 类的方式

另外一种实现线程体的方式是,创建一个 Thread 子类并重写 run() 方法,run() 方法就是线程体函数。

# coding=utf-8
import threading
import time

class MyThread(threading.Thread):
    # 构造器
    def __init__(self, name=None):
        # 调用父类构造器
        super().__init__(name=name)

    # 线程体函数
    def run(self):
        # 当前线程对象
        t = threading.current_thread()
        # 遍历输出
        for n in range(5):
            # 输出
            print("线程{}正在执行....".format(t.name))
            # 睡两秒
            time.sleep(2)
        print("线程{}执行完毕!".format(t.name))

# 实例化线程类
t1 = MyThread(name='线程001')
t2 = MyThread(name='线程002')

# 启动线程
t1.start()
t2.start()

# 等待线程完成
t1.join()
t2.join()

print("所有线程执行完毕!")

2.2 线程管理

线程管理包括线程创建、线程启动、线程休眠、等待线程结束和线程停止。

2.2.1 等待线程结束

有时,一个线程(假设是主线程)需要等待另外一个线程(假设是t1子线程)执行结束才能继续执行。
image
主要调用 join()方法:join(timeout=None)
参数 timeout 用于设置超时时间,单位是秒。如果没有设置 timeout,则可以一直等待,直到结束。

# coding=utf-8
import threading
import time

# 定义一个全局的列表
nums = []

class MyThread(threading.Thread):
    # 构造器
    def __init__(self, name=None):
        # 调用父类构造器
        super().__init__(name=name)

    # 线程体函数
    def run(self):
        # 当前线程对象
        t = threading.current_thread()
        print("线程{}开始......".format(t.name))
        # 遍历输出
        for n in range(5):
            # 输出
            print("线程{}正在执行....".format(t.name))
            # 追加元素
            nums.append(n)
            # 睡两秒
            time.sleep(2)
        print("线程{}执行完毕!".format(t.name))

print("主线程开始........")
# 实例化线程类
t1 = MyThread(name='线程001')
# 启动线程
t1.start()
# 线程等待, 这个地方可将这句话注释再来观察输出效果
t1.join()
# 打印nums
print('nums={}'.format(nums))
print("主线程继续...........")
2.2.2 线程停止

线程体结束时,线程就停止了,但在某些业务比较复杂时,会在线程体中执行一个"死循环"。线程体是否持续执行"死循环"是通过判断停止变量实现的,"死循环"结束则线程体结束,线程也就结束了。
另外,在一般情况下,死循环会执行线程任务,然后休眠,再执行,再休眠,直到结束循环。

# coding=utf-8
import threading
import time

# 全局控制变量
is_working = True

# 工作线程的函数体
def working_thread():
    print("工作线程开始......")
    while is_working:
        print("工作线程运行中.....")
        # 休眠2秒
        time.sleep(2)

# 控制线程函数体
def control_thread():
    # 在线程体中要修改变量值,所以必须global
    global is_working
    print("控制线程开始.....")
    while is_working:
        # 停止的指令输入
        command = input("请输入停止指令:")
        if command == 'exit':
            is_working = False
    print('控制线程结束!')

# 实例化线程
working = threading.Thread(target=working_thread, name='working')
control = threading.Thread(target=control_thread, name='control')

# 开始线程
working.start()
control.start()

# 等待线程完成
working.join()
control.join()

print("所有线程执行完毕!")

0x03 Queue 模块

Python 的 Queue 模块中提供了同步的、线程安全的队列类,包括FIFO(先入先出),队列 Queue,LIFO(后入先出),队列LifoQueue和优先级队列 PriorityQueue。这些队列都实现了锁原语,能够在多线程中直接使用。可以使用队列来实现线程间的同步。

3.1 队列

3.1.1 FIFO

First in First Out:先入先出
queue.Queue(maxsize=0)入参 maxsize 是一个整数,用于设置队列的最大长度,一旦队列达到上限,插入数据将会被阻塞,直到有数据出队列之后才可以继续插入。如果 maxsize 设置为小于或等于零,则队列的长度没有限制。

import queue

q = queue.Queue()  # 创建队列

for i in range(3):
    q.put(i)

for i in range(3):
    print(q.get())
3.1.2 LIFO

Last In First Out:后进先出
queue.LifoQueue(maxsize=0)最后进入队列的数据拥有出队列的优先权,就像栈一样。入参 maxsize 与先进先出队列的定义一样。

import queue

lq = queue.LifoQueue()  # 创建队列

for i in range(3):
    lq.put(i)

for i in range(3):
    print(lq.get())
3.1.3 优先级队列

PriorityQueue(maxsize=0)优先级队列,比较队列中每个元素的大小,值最小的元素拥有出队列的优先权。数据一般以元组的形式插入,典型形式为(priority_number,data)。如果队列中的数据没有可比性,那么数据将被包装在一个类中,忽略数据值,仅仅比较优先级数字。入参 maxsize 与先进先出队列的定义一样。

import queue

pq = queue.PriorityQueue()  # 创建队列
data1 = (1, 'python')
data2 = (2, 'C#')
data3 = (3, 'php')
data_list = [data3, data1, data2]

for data in data_list:
    pq.put(data)  # 依次将data3、data1、data2插入队列中

for i in range(3):
    print(pq.get())  # 依次从队列中取出插入的元素,数据元素输出顺序为data1、data2、data3

3.2 队列方法

Queue.put(item)写入队列,timeout等待时间。
Queue.get([block[, timeout]]) 获取队列,timeout等待时间。
Queue.get_nowait() 相当于Queue.get(False),非阻塞方法。
Queue.qsize() 返回队列的大小。
Queue.empty() 如果队列为空,返回True,反之False。
Queue.full() 如果队列满了,返回True,反之False,Queue.full 与 maxsize 大小对应。
Queue.task_done() 在完成一项工作之后,Queue.task_done()函数向任务已经完成的队列发送一个信号。每个get() 调用得到一个任务,接下来task_done()调用告诉队列该任务已经处理完毕。
Queue.join() 实际上意味着等到队列为空,再执行别的操作。

posted @ 2026-02-27 18:55  77板烧鸡腿堡  阅读(3)  评论(0)    收藏  举报