第112集:线程创建与启动
学习目标
- 掌握Python中创建线程的多种方法
- 学会如何启动和管理线程
- 掌握线程参数传递的方式
- 理解守护线程的概念和应用场景
- 学会设置和获取线程名称
- 了解线程的基本属性和状态
一、线程创建的基本方法
在Python中,threading模块提供了创建和管理线程的功能。我们可以通过以下几种方式创建线程:
1.1 直接创建Thread对象
最简单的方式是直接创建threading.Thread对象,并将需要执行的函数作为参数传递。
import threading
import time
# 定义线程要执行的函数
def worker():
print(f"线程开始执行: {threading.current_thread().name}")
time.sleep(2) # 模拟耗时操作
print(f"线程执行完成: {threading.current_thread().name}")
# 创建线程对象
t = threading.Thread(target=worker)
# 启动线程
t.start()
print(f"主线程: {threading.current_thread().name}")解释:
target参数指定线程要执行的函数start()方法启动线程,使其进入就绪状态threading.current_thread().name获取当前线程的名称
1.2 继承Thread类
我们可以通过继承threading.Thread类并重写run()方法来创建线程。
import threading
import time
# 继承Thread类
class MyThread(threading.Thread):
# 重写run方法
def run(self):
print(f"线程开始执行: {self.name}")
time.sleep(2)
print(f"线程执行完成: {self.name}")
# 创建自定义线程对象
t = MyThread()
# 启动线程
t.start()
print(f"主线程: {threading.current_thread().name}")解释:
- 自定义线程类需要继承
threading.Thread - 必须重写
run()方法,该方法定义了线程要执行的代码 - 调用
start()方法时,会自动执行run()方法
1.3 使用函数创建线程
我们可以将任何可调用对象作为线程的目标函数,包括普通函数、lambda表达式和类的实例方法。
import threading
import time
# 使用lambda表达式创建线程
t1 = threading.Thread(target=lambda: print("Hello from lambda thread"))
t1.start()
# 使用实例方法创建线程
class MyClass:
def worker(self, name):
print(f"Worker {name} started")
time.sleep(1)
print(f"Worker {name} finished")
my_obj = MyClass()
t2 = threading.Thread(target=my_obj.worker, args=("Alice",))
t2.start()解释:
- 任何可调用对象都可以作为
target参数 - 对于实例方法,需要提供对象实例和方法参数
二、线程参数传递
线程执行的函数通常需要参数,我们可以通过以下方式传递参数:
2.1 使用args传递位置参数
import threading
import time
def worker(name, delay):
print(f"Worker {name} started with delay {delay}s")
time.sleep(delay)
print(f"Worker {name} finished")
# 使用args传递位置参数
t1 = threading.Thread(target=worker, args=("Alice", 1))
t2 = threading.Thread(target=worker, args=("Bob", 2))
# 启动线程
t1.start()
t2.start()解释:
args参数接受一个元组,包含传递给目标函数的位置参数- 参数的顺序必须与函数定义的顺序一致
2.2 使用kwargs传递关键字参数
import threading
import time
def worker(name, delay):
print(f"Worker {name} started with delay {delay}s")
time.sleep(delay)
print(f"Worker {name} finished")
# 使用kwargs传递关键字参数
t1 = threading.Thread(target=worker, kwargs={"name": "Alice", "delay": 1})
t2 = threading.Thread(target=worker, kwargs={"name": "Bob", "delay": 2})
# 启动线程
t1.start()
t2.start()解释:
kwargs参数接受一个字典,包含传递给目标函数的关键字参数- 字典的键必须与函数参数名一致
2.3 同时使用args和kwargs
import threading
import time
def worker(name, delay, message=""):
print(f"Worker {name} started with delay {delay}s{message}")
time.sleep(delay)
print(f"Worker {name} finished")
# 同时使用args和kwargs
t = threading.Thread(target=worker, args=("Alice", 1), kwargs={"message": " - special task"})
t.start()解释:
- 可以同时使用
args和kwargs传递参数 args中的参数会先传递,然后是kwargs中的参数
三、线程名称管理
每个线程都有一个名称,可以通过以下方式设置和获取:
3.1 设置线程名称
import threading
def worker():
print(f"当前线程名称: {threading.current_thread().name}")
# 使用name参数设置线程名称
t1 = threading.Thread(target=worker, name="MyThread-1")
t2 = threading.Thread(target=worker) # 使用默认名称
# 启动线程
t1.start()
t2.start()解释:
- 通过
name参数可以设置线程的名称 - 如果不指定名称,系统会自动生成类似"Thread-1"的名称
3.2 获取线程名称
import threading
def worker():
# 获取当前线程对象
current_thread = threading.current_thread()
# 获取线程名称
thread_name = current_thread.name
print(f"当前线程名称: {thread_name}")
# 创建并启动线程
t = threading.Thread(target=worker, name="CustomThread")
t.start()
# 获取线程对象的名称
print(f"线程对象名称: {t.name}")解释:
threading.current_thread()返回当前正在执行的线程对象- 通过线程对象的
name属性可以获取线程名称
四、守护线程
4.1 守护线程的概念
守护线程(Daemon Thread)是一种特殊的线程,当主线程结束时,所有守护线程会自动终止,无论它们是否执行完成。
守护线程通常用于执行一些后台任务,如日志记录、监控等,这些任务不需要等待完成。
4.2 设置守护线程
import threading
import time
def daemon_worker():
print("守护线程开始执行")
time.sleep(5) # 模拟长时间运行
print("守护线程执行完成")
def normal_worker():
print("普通线程开始执行")
time.sleep(2) # 模拟短时间运行
print("普通线程执行完成")
# 创建守护线程
# 方式1:通过构造参数设置
daemon_thread = threading.Thread(target=daemon_worker, daemon=True)
# 方式2:通过属性设置
# daemon_thread = threading.Thread(target=daemon_worker)
# daemon_thread.daemon = True
# 创建普通线程
normal_thread = threading.Thread(target=normal_worker)
# 启动线程
daemon_thread.start()
normal_thread.start()
print("主线程睡眠1秒")
time.sleep(1)
print("主线程结束")执行结果:
守护线程开始执行
普通线程开始执行
主线程睡眠1秒
主线程结束
普通线程执行完成解释:
- 守护线程设置为
daemon=True - 主线程结束后,守护线程被强制终止,没有执行到"守护线程执行完成"的打印
- 普通线程会继续执行直到完成
4.3 守护线程的应用场景
守护线程适用于以下场景:
- 后台服务:如定时检查、监控任务
- 日志记录:后台记录程序运行日志
- 资源清理:自动清理临时文件或缓存
- 辅助任务:为主线程提供辅助功能的任务
注意事项:
- 守护线程不能访问共享资源的临界区,因为它们可能在任何时候被终止
- 守护线程中不应该执行需要确保完成的操作,如文件写入、数据库事务等
五、线程的基本属性和方法
5.1 线程的基本属性
| 属性/方法 | 描述 |
|---|---|
name |
线程名称 |
daemon |
是否为守护线程 |
ident |
线程标识符 |
is_alive() |
线程是否处于活动状态 |
join([timeout]) |
等待线程终止,可选超时时间 |
start() |
启动线程 |
run() |
线程执行的目标函数,通常不需要直接调用 |
5.2 线程的状态
线程从创建到终止会经历以下状态:
- 新建(New):线程对象已创建,但尚未启动
- 就绪(Runnable):线程已启动,等待CPU调度
- 运行(Running):线程正在CPU上执行
- 阻塞(Blocked):线程等待某个事件完成(如I/O操作、锁获取)
- 死亡(Dead):线程执行完成或被终止
5.3 获取线程状态
import threading
import time
def worker():
time.sleep(2)
# 创建线程
thread = threading.Thread(target=worker, name="MyWorker")
# 线程创建后(新建状态)
print(f"线程名称: {thread.name}")
print(f"是否为守护线程: {thread.daemon}")
print(f"线程标识符: {thread.ident}") # None,因为线程尚未启动
print(f"线程是否活动: {thread.is_alive()}") # False
# 启动线程
thread.start()
print(f"\n线程启动后:")
print(f"线程标识符: {thread.ident}") # 有值
print(f"线程是否活动: {thread.is_alive()}") # True
# 等待线程完成
thread.join()
print(f"\n线程结束后:")
print(f"线程是否活动: {thread.is_alive()}") # False六、线程创建与启动的实际应用
6.1 多线程下载示例
import threading
import time
# 模拟下载文件的函数
def download_file(filename, size, speed):
print(f"开始下载: {filename} ({size}MB)")
downloaded = 0
while downloaded < size:
# 模拟下载进度
downloaded += speed
if downloaded > size:
downloaded = size
print(f"{filename}: {downloaded}/{size} MB ({(downloaded/size)*100:.1f}%)")
time.sleep(0.5) # 模拟下载间隔
print(f"下载完成: {filename}")
# 下载任务列表
download_tasks = [
("movie.mp4", 500, 50), # (文件名, 大小(MB), 下载速度(MB/s))
("music.mp3", 100, 20),
("document.pdf", 50, 10)
]
# 创建并启动线程
threads = []
for task in download_tasks:
filename, size, speed = task
t = threading.Thread(target=download_file, args=(filename, size, speed))
threads.append(t)
t.start()
# 等待所有线程完成
for t in threads:
t.join()
print("所有文件下载完成")6.2 多线程数据处理示例
import threading
import time
# 数据处理函数
def process_data(data_chunk, processor_id):
print(f"处理器 {processor_id} 开始处理数据块")
# 模拟数据处理
processed_data = [item * 2 for item in data_chunk]
time.sleep(1)
print(f"处理器 {processor_id} 处理完成,结果: {processed_data}")
return processed_data
# 原始数据
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# 将数据分成块
chunk_size = 3
data_chunks = [data[i:i+chunk_size] for i in range(0, len(data), chunk_size)]
# 创建并启动线程处理数据
threads = []
results = []
for i, chunk in enumerate(data_chunks):
# 使用lambda捕获参数
t = threading.Thread(target=lambda c, idx: results.extend(process_data(c, idx)), args=(chunk, i+1))
threads.append(t)
t.start()
# 等待所有线程完成
for t in threads:
t.join()
print(f"\n所有数据处理完成,总结果: {results}")七、线程安全与同步
在多线程环境下,当多个线程同时访问共享资源时,可能会导致数据不一致的问题。在后续的课程中,我们将学习线程同步机制,如锁、信号量等,来解决这些问题。
import threading
# 共享变量
counter = 0
# 创建锁
lock = threading.Lock()
def increment():
global counter
for _ in range(100000):
with lock: # 获取锁
counter += 1 # 临界区操作
# 自动释放锁
# 创建两个线程
t1 = threading.Thread(target=increment)
t2 = threading.Thread(target=increment)
# 启动线程
t1.start()
t2.start()
# 等待线程完成
t1.join()
t2.join()
print(f"最终计数器值: {counter}") # 200000解释:
threading.Lock()创建一个锁对象with lock:语句确保在进入临界区前获取锁,退出时自动释放锁- 这样可以确保
counter += 1操作的原子性
八、总结与练习
8.1 总结
线程创建方法:
- 直接创建Thread对象
- 继承Thread类
- 使用函数、lambda表达式或实例方法
参数传递:
- 使用
args传递位置参数 - 使用
kwargs传递关键字参数 - 同时使用
args和kwargs
- 使用
线程管理:
- 设置和获取线程名称
- 设置守护线程
- 获取线程状态和属性
- 使用
join()等待线程完成
守护线程:
- 主线程结束时自动终止
- 适用于后台任务
- 不适合执行需要确保完成的操作
8.2 练习
基础练习:
- 编写一个程序,创建3个线程,分别打印1-10、11-20、21-30的数字
- 每个线程执行完成后,打印线程名称和完成信息
进阶练习:
- 实现一个多线程下载器,模拟下载5个不同大小的文件
- 使用守护线程实现一个简单的进度监控功能
- 编写一个程序,演示主线程结束时守护线程的行为
思考问题:
- 守护线程和普通线程的区别是什么?
- 线程参数传递有哪几种方式?各有什么优缺点?
- 为什么需要设置线程名称?在实际应用中有什么作用?
九、扩展阅读
Python官方文档:
- threading模块:https://docs.python.org/3/library/threading.html
推荐书籍:
- 《Python Cookbook》(第3版)第12章:并发编程
- 《Fluent Python》第17章:并发执行
在线资源:
下集预告:第113集将学习线程同步机制中的锁机制,包括如何使用锁来解决线程安全问题,以及不同类型的锁的应用场景。