多线程
GIL
- 全局解释器锁,保证同一时刻只有一个线程可以执行代码。
- GIL是一个历史遗留问题,主要出现在C语言解释器上,导致python的多线程是伪多线程。
- 虽然Python的多线程是伪多线程,计算密集型任务单线程比多线程快,IO密集型任务多线程比单线程慢。
- 所以在python中如果想要尽可能高的利用多核CPU的性能,还是要使用多进程。
threading模块
- 此模块的是Python自带的用于实现多线程的模块。
- 创建线程的两种方法:
#-------------------------------------------------------------
# 函数式创建
#-------------------------------------------------------------
import threading
# 定义一个函数,作为线程任务
def game():
print('--------game--------')
def main():
# 创建一个线程
# target参数传入函数名称
t1 = threading.Thread(target=game)
# 启动一个线程
t1.start()
if __name__ = '__main__':
main()
#-------------------------------------------------------------
# 创建线程类
#-------------------------------------------------------------
class MyThead(threading.Thread):
#run函数是必须有的,线程启动后首先执行run函数里的代码(里面不一定是函数)
def run(self):
function()
def function(self):
pass
if __name__ = "__main__":
# 创建线程
for i in range(10):
t = MyThread()
t.run()
setDeamon()、join()
setDeamon()
- 当创建多个线程后,默认的是主线程执行完后,等待子线程执行完毕,然后退出程序。
- 但设置了守护线程后,一切以主线程为主,一旦主线程执行完毕,就会立即结束所有的子线程,不论子线程是否结束,然后退出程序。
t1 = threading.Thread(target=function)
t1.setDeamon(True)
join()
-
如果不设置join(),主线程在子线程启动后,会继续往下执行。
-
设置了join()后,主线程会等待子线程运行结束后再继续往下运行。
-
没有设置join()
import threading
import time
def funcA():
print("funcA启动")
time.sleep(2)
print("funcA结束")
return
def funcB():
print("funcB启动")
time.sleep(2)
print("funcB结束")
return
if __name__ == '__main__':
start = time.time()
t1 = threading.Thread(target=funcA)
t2 = threading.Thread(target=funcB)
t1.start()
t2.start()
total = time.time() - start
print(f"耗时{total}")
"""
funcA启动
funcB启动
耗时0.0
funcB结束funcA结束
"""
- 设置了join()
import threading
import time
def funcA():
print("funcA启动")
time.sleep(2)
print("funcA结束")
return
def funcB():
print("funcB启动")
time.sleep(3)
print("funcB结束")
return
if __name__ == '__main__':
start = time.time()
t1 = threading.Thread(target=funcA)
t2 = threading.Thread(target=funcB)
t1.start()
t2.start()
t1.join()
t2.join()
total = time.time() - start
print(f"耗时{total}")
"""
funcA启动
funcB启动
funcA结束
funcB结束
耗时3.0100889205932617
"""
线程间通信
-
全局变量。线程间是共享全局变量的。所以可以用全局变量来进行线程间通信。太low了,我就不说了。
-
队列。队列Queue是python标准库中的线程安全的队列(FIFO)实现,提供了一个适用于多线程编程的先进先出的数据结构,即队列,用来在生产者和消费者线程之间的信息传递
-
使用方法:
from queue import Queue
# 实例化该数据结构,设置数据量上限1000条,如果不设置则没有上限
q = Queue(maxsize=1000)
# 向队列中推入数据
q.put()
# 向队列中取数据
q.get()
# 获取当前队列数据条数
q.qsize()
# 判断当前队列是否已满
q.full()
# 判断队列是否为空
q.empty()
# 等到队列为空再执行别的操作
q.join()
互斥锁
- 当同一时刻有多个线程对共有资源(全局变量)进行操作时,可能会出现资源竞争,所以我们需要互斥锁,确保同一时刻只能有一个线程对共有资源进行操作,避免出现资源竞争,导致结果错误。
- 资源竞争:
# 示例:
import threading
import time
num = 0
def one():
global num
# 上锁
lock1.acquire()
for i in range(1000000):
num += 1
# 解锁
lock1.release()
def two():
global num
for j in range(1000000):
lock1.acquire()
num += 1
lock1.release()
if __name__ == '__main__':
t1 = threading.Thread(target=one)
t2 = threading.Thread(target=two)
t1.start()
t2.start()
# 休眠两秒,确保结果是最终结果
time.sleep(2)
print(num)
# 当同时有两个线程对全局变量num进行累加操作时,因为没有互斥锁,所以会导致
# 资源竞争导致最后的结果不是我们想要的200000
- 上锁后,同一时刻只能有一个线程对num进行加一操作:
# 示例:
import threading
import time
num = 0
def one():
global num
# 上锁
lock1.acquire()
for i in range(1000000):
num += 1
# 解锁
lock1.release()
def two():
global num
for j in range(1000000):
lock1.acquire()
num += 1
lock1.release()
if __name__ == '__main__':
# 创建互斥锁
lock1 = threading.Lock()
t1 = threading.Thread(target=one)
t2 = threading.Thread(target=two)
t1.start()
t2.start()
# 休眠两秒,确保结果正确
time.sleep(2)
print(num)
# num 2000000
死锁
- 出现死锁的两种情况:
- 相互等待
- 自我等待
- 以下是相互等待的示例:
import threading
import time
def funA(lockA, lockB):
lockA.acquire()
print("funA get lockA")
# 让funA休眠两秒,确保funB拿到B锁,人为使之形成死锁
time.sleep(2)
lockB.acquire()
print("funA get lockB")
lockB.release()
lockA.release()
def funB(lockA, lockB):
lockB.acquire()
print("funB get lockA")
lockA.acquire()
print("funA get lockB")
lockA.release()
lockB.release()
if __name__ == '__main__':
lockA = threading.Lock()
lockB = threading.Lock()
t1 = threading.Thread(target=funA)
t2 = threading.Thread(target=funB)
t1.start()
t2.start()
递归锁(可重入锁)
- 递归锁允许一个线程多次获取同一把锁。
- 但是线程之间如果某线程获取了锁,其他线程还是要等待。
import threading
num = 0;
def funcA():
for i in range(1,10000):
# 采用递归锁就不会产生死锁
lock.acquire()
lock.acquire()
num += 1
lock.release()
lock.release()
if __name__ = "__main__":
lock = threading.Rlock()
t1 = threading.Tread(target=funcA)
条件变量condition
-
条件变量用于复杂的线程间同步
-
条件变量其实就是多个线程间在底层公用一把线程锁,可以是普通锁,也可以是递归锁。默认是递归锁。
-
示例,简单生产者消费者模型:
from threading import Condition, Thread
import random, time
class Producer(Thread):
def __init__(self, nums, condition):
super().__init__()
self.nums = nums
self.cond = condition
def run(self):
while True:
self.cond.acquire()
num = random.randint(1, 101)
print("产生一个随机数{}".format(num))
time.sleep(0.5)
self.nums.append(num)
# 通知消费者
self.cond.notify()
# 挂起当前线程,并等待消费者通知
self.cond.wait()
class Customer(Thread):
def __init__(self, nums, condition):
super().__init__()
self.nums = nums
self.cond = condition
def run(self):
while True:
self.cond.acquire()
num = self.nums.pop()
print("消费者消费一个随机数{}".format(num))
time.sleep(0.5)
# 通知生产者
self.cond.notify()
# 挂起当前线程,并等待生产者通知
self.cond.wait()
if __name__ == '__main__':
condition = Condition()
# 用列表作为线程间的共享资源,其实不严谨,在这里只是简单演示
nums = []
p = Producer(nums, condition)
c = Customer(nums, condition)
p.start()
c.start()
"""
产生一个随机数1
消费者消费一个随机数1
产生一个随机数6
消费者消费一个随机数6
产生一个随机数7
消费者消费一个随机数7
产生一个随机数19
消费者消费一个随机数19
产生一个随机数12
消费者消费一个随机数12
.....
"""