一、线程生命周期
一个线程完整的生命周期包括新建——就绪——运行——阻塞——死亡。
新建:即新创建一个线程对象
就绪:调用start方法后,线程对象等待运行,什么时候开始运行取决于调度
运行:线程处于运行状态
阻塞:处于运行状态的线程被堵塞,通俗理解就是被卡住了,可能的原因包括但不限于程序自身调用sleep方法阻塞线程运行,或调用了一个阻塞式I/O方法,被阻塞的进程会等待何时解除阻塞重新运行
死亡:线程执行完毕或异常退出,线程对象被销毁并释放内存
二、创建线程
(1)直接使用Thread创建线程对象
Thread类创建新线程的基本语法如下:
Newthread = Thread(target=function, args=(argument1,argument2,...))
Newthread: 创建的线程对象
function: 要执行的函数
argument1,argument2: 传递给线程函数的参数,为tuple类型
示例:
from threading import Thread
import time
from time import sleep
# 自定义的函数,可以替换成其他任何函数
def task(threadName, number, letter):
print(f"【线程开始】{threadName}")
m = 0
while m < number:
sleep(1)
m += 1
current_time = time.strftime('%H:%M:%S', time.localtime())
print(f"[{current_time}] {threadName} 输出 {letter}")
print(f"【线程结束】{threadName}")
thread1 = Thread(target=task, args=("thread_1", 4, "a")) # 线程1:执行任务打印4个a
thread2 = Thread(target=task, args=("thread_2", 2, "b")) # 线程2:执行任务打印2个b
thread1.start() # 线程1开始
thread2.start() # 线程2开始
thread1.join() # 等待线程1结束
thread2.join() # 等待线程2结束
输出:
【线程开始】thread_1
【线程开始】thread_2
[18:31:21] thread_1 输出 a
[18:31:21] thread_2 输出 b
[18:31:22] thread_1 输出 a
[18:31:22] thread_2 输出 b
【线程结束】thread_2
[18:31:23] thread_1 输出 a
[18:31:24] thread_1 输出 a
【线程结束】thread_1
(2)使用join阻塞线程
join方法可以用于阻塞线程的顺序执行,因此,在主线程中使用join可以调整各个子线程的执行顺序
thread1.start() # 线程1启动
thread2.start() # 任务2启动
thread2.join() # 等待线程2
thread3.start() # 线程2完成任务后线程3才启动
thread1.join() # 等待线程1完成线程
thread3.join() # 等待线程3完成线程
三、重写父类threading.Thread线程类
前台线程与后台线程:
threading.Thread的daemon参数,其默认为False,表示线程默认就是一个前台线程,前台线程表示当所有的前台线程都执行完毕时,整个程序才退出。将daemon参数设定为True是表示线程是一个后台线程,此时主进程结束时,所有未执行完成的后台线程也都会直接自动结束。
示例:在初始化部分加入self.daemon=True,并去掉末尾的join方法,替换成sleep方法来阻塞主程序的运行
import threading
import time
from time import sleep
# myThread继承父类,并进行重写
class myThread(threading.Thread):
# 重写父类的构造函数
def __init__(self, number, letter):
threading.Thread.__init__(self)
self.number = number # 添加number变量
self.letter = letter # 添加letter变量
self.daemon = True # 后台线程
# 重写父类中的run函数
def run(self):
print(f"【线程开始】{self.name}")
task1(self.name, self.number, self.letter)
# task2...
# task3...
print("【线程结束】", self.name)
# 重写父类析构函数
def __del__(self):
print("【线程销毁释放内存】", self.name)
# 自定义的函数,此处可以替换成任何其他想要多线程执行的任务
def task1(threadName, number, letter):
m = 0
while m < number:
sleep(1)
m += 1
current_time = time.strftime('%H:%M:%S', time.localtime())
print(f"[{current_time}] {threadName} 输出 {letter}")
# def task2...
# def task3...
thread1 = myThread(4, "a") # 创建线程thread1:任务耗时2s
thread2 = myThread(2, "b") # 创建线程thread2:任务耗时4s
thread1.start() # 启动线程1
thread2.start() # 启动线程2
thread1.join() # 等待线程1
thread2.join() # 等待线程2
time.sleep(3) # 主程序等待3s再继续执行
输出:
【线程开始】Thread-1
【线程开始】Thread-2
[10:31:45] Thread-1 输出 a
[10:31:45] Thread-2 输出 b
[10:31:46] Thread-1 输出 a
[10:31:46] Thread-2 输出 b
【线程结束】 Thread-2
我们用sleep方法强行阻塞了主程序3s,但是由于我们将线程设定为了后台线程,3s过后,主程序将执行完毕,此时两个子线程thread1和thread2无论是否执行完成,都将强行结束。
四、线程同步(线程锁)
为了将各个线程同步,我们引入线程锁的概念。当某个线程访问数据时,先对其加锁,其他线程若再想访问这个数据就会被阻塞,直到前一个线程解锁释放。在threading模块中,加锁和释放锁主要使用Lock类,使用其中的acquire()和release()方法:
1、使用threading.Lock
来保护共享资源
Lock = threading.Lock() # 在threading模块中获得锁类
Lock.acquire() # 设置锁
Lock.release() # 释放锁
示例:
使用多线程对一个列表list进行数据删改。
import threading
import time
# 子类myThread继承父类threading.Thread,并进行重写
class myThread(threading.Thread):
# 重写父类构造函数
def __init__(self, number):
threading.Thread.__init__(self)
self.number = number
# 重写父类run函数,在调用start()时自动调用run函数
def run(self):
print(f"【线程开始】{self.name}")
Lock.acquire() # 设置线程锁
edit_list(self.name, self.number)
Lock.release() # 释放线程锁
# 重写父类析构函数
def __del__(self):
print("【线程销毁】", self.name)
# 自定义的任务函数
def edit_list(threadName, number):
while number > 0:
time.sleep(1)
data_list[number-1] += 1
current_time = time.strftime('%H:%M:%S', time.localtime())
print(f"[{current_time}] {threadName} 修改datalist为{data_list}")
number -= 1
print(f"【线程{threadName}完成工作】")
data_list = [0, 0, 0, 0]
Lock = threading.Lock()
# 创建3个子线程
thread1 = myThread(1)
thread2 = myThread(2)
thread3 = myThread(3)
# 启动3个子线程
thread1.start()
thread2.start()
thread3.start()
# 主进程等待所有线程完成
thread1.join()
thread2.join()
thread3.join()
print("【主进程结束】")
输出:
【线程开始】Thread-1
【线程开始】Thread-2
【线程开始】Thread-3
[09:55:22] Thread-1 修改datalist为[1, 0, 0, 0]
【线程Thread-1完成工作】
[09:55:23] Thread-2 修改datalist为[1, 1, 0, 0]
[09:55:24] Thread-2 修改datalist为[2, 1, 0, 0]
【线程Thread-2完成工作】
[09:55:25] Thread-3 修改datalist为[2, 1, 1, 0]
[09:55:26] Thread-3 修改datalist为[2, 2, 1, 0]
[09:55:27] Thread-3 修改datalist为[3, 2, 1, 0]
【线程Thread-3完成工作】
【主进程结束】
【线程销毁】 Thread-1
【线程销毁】 Thread-2
【线程销毁】 Thread-3
2、条件变量(Condition)
使用threading.Condition
来控制线程间的通信。
import threading
cond = threading.Condition()
def consumer():
with cond:
cond.wait() # 等待通知
# 消费数据
def producer():
with cond:
# 生产数据
cond.notify() # 通知消费者
consumer_thread = threading.Thread(target=consumer)
producer_thread = threading.Thread(target=producer)
consumer_thread.start()
producer_thread.start()
3、事件(Event)
使用threading.Event
来通知线程某个事件发生。
import threading
event = threading.Event()
def wait_for_event():
event.wait() # 等待事件被设置
# 事件触发后的代码
def signal_event():
# 某些操作
event.set() # 设置事件
thread = threading.Thread(target=wait_for_event)
thread.start()
signal_event()
thread.join()