GIL锁 – 全局解释器锁
- 它是 cpython 解释器的特性, 并非 python 的
- 在同一时刻同一进程, 只能有一个线程被 cpu 执行, 用来保证数据安全(牺牲多核优势)
- IO 等待时, 会被系统强制释放 GIL 锁, 被另一线程拿到, 执行另一线程的工作
所以若程序为高IO型, GIL锁机制并没有太多影响, 因为全是IO等待… - 高计算型是需要大量访问cpu进行计算, 而python的多线程限制了这一点, 所以python并没有优势
开启线程
- 即使主线程结束, 仍然会等待子线程结束, 才会结束整体
- start() 表示开启线程
- join() 表示主线程等待该子线程结束才能结束
- 不支持异步回调机制(貌似)
函数版
- 不能获取每个线程的返回值, 并进行后续处理
from threading import Thread
import time
import random
# 用于并发执行的函数
def task(num):
time.sleep(random.randint(1, 3))
print(num)
if __name__ == "__main__":
# 创建一个用于存放线程的 list
# 这种写法如果创建的线程过多, 可能会把性能打死, 所以后续有了线程池的概念
l = []
# 本例中, 索引即使参数, 所以可以直接 append
# 如果不是, 可以创建一个参数列表(想法供参考)
for i in range(1, 6):
t = Thread(target=task, args=(i,))
l.append(t)
# 将线程启动
t.start()
# 阻塞到所有线程执行完成
for i in l:
i.join()
print("end")
类版
- 需要自己定义 run() 方法
- 一个类只能起一个线程(不确定)
- 传参需要在实例化时候传为属性
- 使用方法和
函数版
相同, start()+join() - 传可以获取返回值(将结果存为 obj 属性)
from threading import Thread
import time
import random
class T(Thread):
def __init__(self, num):
super().__init__()
self.num = num
self.result = None
# 类中方法开并发会找 run 函数
def run(self):
time.sleep(random.randint(1, 3))
self.result = self.num ** 2
# 取返回值的方法
def get_result(self):
return self.result
if __name__ == '__main__':
l = []
for i in range(5):
t = T(i)
l.append(t)
t.start()
for i in l:
i.join()
# 输出返回值
for i in l:
print(i.get_result())
print("end")
守护线程
- 当所有非守护线程(包括主线程)结束时, 守护线程同时结束
- 守护线程需要根据场景考虑是否设置 join() 方法
设置守护模式
# 前后代码省略, 使用 t.daemon = True 设置线程为守护线程
t = Thread(target=xxx)
t.daemon = True
报活示例
""" 当主线程回收所有资源之后, 守护线程才会结束 """
from threading import Thread
import time
import random
# 用于模拟执行任务
def task():
# 模拟执行 5 次
for i in range(5):
time.sleep(random.randint(1, 3))
print("%s finish" % i)
# 作为守护线程
def check():
while True:
print("still alive..")
time.sleep(0.5)
if __name__ == "__main__":
t = Thread(target=task)
d = Thread(target=check)
d.daemon = True
t.start()
d.start()
# 如果需要等待 task 执行完毕在结束主线程, 则要设置 t.join()
t.join()
print("end")
高级用法
线程锁 - 基于全局变量操作
from threading import Thread, Lock
import time
def task():
# 区别3: 使用全局变量
global n
# 区别4: 基于锁去做处理
with mutex:
print(n)
time.sleep(0.1) # 模拟计算耗时
n -= 1
if __name__ == "__main__":
# 区别1: 引入一个锁
mutex = Lock()
l = []
# 区别2: 定义 global 变量
n = 100
for i in range(n):
t = Thread(target=task)
l.append(t)
t.start()
for i in l:
i.join()
print("end")
信号量 - semaphore
- 类似于池, 正在执行的线程为固定数量
- 完成一个, 再执行一个
from threading import Thread, Semaphore, current_thread
import time, random
sm = Semaphore(5)
def task():
with sm:
print("%s is laing" % current_thread().getName())
time.sleep(random.randint(1, 3))
# time.sleep(1)
if __name__ == "__main__":
for i in range(20):
t = Thread(target=task)
t.start()
事件 - Event - 等待状态变更
from threading import Thread, Event
import time
def check():
print("checking mysql...")
# 5 秒后, event 对象状态设置为 True
time.sleep(5)
print("check complate")
e.set()
# 只有当事件状态为 True 时, 才能执行, 否则会重试
def conn():
# 重试 3 次, 计数器置为 1
count = 1
# 如果事件状态为 false
while not e.is_set():
# 如果重试次数超过3次, 报错
if count > 3:
raise TimeoutError("超时")
# 如果不超过3次, 设置事件等待时间, 且计数器+1
print("try to connect Mysql time %s" % count)
e.wait(2)
count += 1
# 如果时间状态为True(e.set())执行下边的代码
print("connect complate")
if __name__ == "__main__":
# 实例化一个事件对象
e = Event()
# 打印此时事件的状态, isSet = is_set
print(e.is_set())
# 起一个线程, 模拟状态变化
t1 = Thread(target=check)
t1.start()
t2 = Thread(target=conn, name="t2")
t3 = Thread(target=conn, name="t3")
t2.start()
t3.start()
# 重置信号为 False
e.clear()
计时器 - Timer
from threading import Timer
import time
def hello(i, name):
time.sleep(i)
print(name)
if __name__ == '__main__':
l = []
# 等待 2 秒, 睡 3 秒
t1 = Timer(2, hello, args=(3, "Tim"))
l.append(t1)
# 等待 3 秒, 睡 2 秒
t2 = Timer(3, hello, args=(2, "Tom"))
l.append(t2)
# 等待 1 秒, 睡 1 秒
t3 = Timer(1, hello, args=(1, "Sam"))
l.append(t3)
for i in l:
i.start()
for i in l:
i.join()
print("end")