一、验证GIL锁的存在
我们如何验证GIL锁呢?我们知道因为有GIL锁所以我们所谓的多线程实际上只是一个线程,即使我们开很多个线程,其实也只是利用到了CPU的一个核心,而进程就不一样了,一个进程拥有一个GIL所以,我们开几个进程实际就是利用几个CPU
from threading import Thread
from multiprocessing import Process
def run():
while True:
pass
if __name__ == '__main__':
for i in range(100):
r1 = Thread(target=run)
# r2 = Process(target=run)
r1.start()
一下是运行线程时CPU的状态,并没有跑满
二、GIL锁和普通锁的区别
GIL锁并不能保证数据的安全,普通互斥锁来保证数据的安全
举个栗子
1 GIL锁是不能保证数据的安全,普通互斥锁来保证数据安全
from threading import Thread, Lock
import time
mutex = Lock()
money = 100
def task():
global money
temp = money
time.sleep(1)
money = temp - 1
if __name__ == '__main__':
for i in range(10):
t = Thread(target=task)
t.start()
time.sleep(2) # 防止还没执行完毕 数据还没修改
print(money)
------------------------------------
99
解释GIL锁的运行原理,我们开了是个线程,当第一个线程拿到GIL锁时,temp=money=100,然后下一步进入io操作,第二个线程拿到GIL锁,也执行temp=money但是此时money并没有修改,所以仍然是100,诸如此类,直到最后一个,如果我们没有赋值操作那么可以得到正确的结果
三、死锁现象
死锁
两个或者两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,他们两个都无法推进下去。此时称称死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。死锁
def run(name):
A.acquire()
print(f"{name}拿到了A锁")
B.acquire()
print(f"{name}拿到了B锁")
B.release()
print(f"{name}释放了B锁")
A.release()
print(f"{name}释放了A锁")
def start(name):
B.acquire()
print(f"{name}也拿到了B锁")
A.acquire()
time.sleep(2)
print(f"{name}也拿到了A锁")
A.release()
print(f"{name}也释放了A锁")
B.release()
print(f"{name}也释放了B锁")
if __name__ == '__main__':
name_l = ["gg", "ss", "ff"]
for i in name_l:
r = Thread(target=run, args=(i,))
s = Thread(target=start, args=(i,))
s.start()
r.start()
------------------------------------------
>>> gg也拿到了B锁
>>> gg也拿到了A锁
>>> gg也释放了A锁
>>> gg也释放了B锁 #由于启动线程需要花费时间,所以s先启动先拿到需要的两把锁BA,此时两把锁在自己手上,能够正常释放
>>> ss也拿到了B锁 # 此时s再次启动抢到的第一把锁B
>>> gg拿到了A锁 # 与此同时r启动也抢到了自己需要的第一把锁A
这个时候就有趣了起来s还需要一把锁A r还需要一把锁B,但此时这两把锁都在对方手上并没有释放,所以都陷入等待时间
解决死锁
要解决死锁我们可以规范自己的代码,个人感觉使用上下文管理器原理可以避免,先拿先放,再改再锁,同时也可以导入RLock
类来解决问题。也就是递归锁
递归锁:这个RLock
内部维护着一个Lock
和一个counter
计数器变量,每acquire
一次,内部计数器加1,每relaese
一次,内部计数器减一只有计数器不为0,其他人都不获得这把锁
我们只需要将源代码
from threading import Thread, RLock
# A = Lock()
# B = Lock()
改为
A = B = RLock() # 如果我们这里还是用两种不同的锁,就和死锁没有区别,因为我们根本没有用上解决问题的 计数功能
四、semaphore信号量
threading模块里的Semaphore
类实现了信号量对象,可用于控制获取资源的线程数量。所具有的acquire()
和release()
方法,可以用with语句的上下文管理器。当进入时,将调用acquire()
方法,当退出时,将调用release()
。
Semaphore的value参数表示内部计数器的初始值,默认值为0。信号量内部有个计数器,它的计算方式:
- 每当调用acquire()时内置计数器-1;调用release() 时内置计数器+1
- 计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。
- 如果我们把semaphore控制为3,也就是说,同时有3个线程可以用这个锁,剩下的线程也之只能是阻塞等待。
import time
import random
sm = Semaphore(3) # 数字表示可以同时有多少个线程操作
def task(name):
sm.acquire()
print('%s 正在蹲坑' % name)
time.sleep(random.randint(1, 5))
sm.release()
if __name__ == '__main__':
for i in range(100):
t = Thread(target=task, args=(i,))
t.start()
--------------------------------
>>> 0 正在蹲坑
>>> 1 正在蹲坑
>>> 2 正在蹲坑
# 会立马执行前三个线程
>>> 3 正在蹲坑
>>> 4 正在蹲坑 #后面这两个就变为串行操作了 一个完成了再进行下一个 很像上厕所有没有
五、Event事件
线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其 他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就会变得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。对象包含一个可有线程设置的信号标志,它允许线程等待某些时间的发生
- 在初始情况下,Event对象中的信号标志被设为False
- 如果有线程等待一个Event对象,而这个Event对象的标志为False,那么这个线程会被一直阻塞直至该标志为True。
- 如果一个线程将Event对象的信号标志设置为True,他将唤醒所有等待这个Event对象的线程。
- 如果一个线程等待一个已经被设置为True的Event对象,那么它将忽略这个事件, 继续执行
基本方法
flag = threading.Event() 这个对象里面的值默认是 False
flag.wait()
:查看 flag 里面的值,如果这个值是 false 的话,就在这里等待它变成 True ,然后再执行后面的代码,括号里面可以写参数,意思为 超时执行flag.set()
:把 flag 里面的值改为 Trueevent.isSet()
:返回event的状态值flag.clear()
: 把 flag 里面的值改为 False
import time
from threading import Thread, Event
def Shoes_on_sale():
print("Nike准备5秒钟后发售新的AJ配色!")
time.sleep(5)
print("AJ韭菜配色正在发售")
event.set()
def Chives(i):
print(f"等待被收割的韭菜{i}号!")
event.wait()
print(f"我是韭菜{i}号,我已准备好抢鞋子了")
if __name__ == '__main__':
event = Event()
s = Thread(target=Shoes_on_sale)
s.start()
for i in range(10):
c = Thread(target=Chives, args=(i+1, ))
c.start()
---------------------------------------------------
>>> Nike准备5秒钟后发售新的AJ配色!
>>> 等待被收割的韭菜1号!
>>> 等待被收割的韭菜2号!
>>> 等待被收割的韭菜3号!
....................
>>> AJ韭菜配色正在发售
>>> 我是韭菜8号,我已准备好抢鞋子了
>>> 我是韭菜4号,我已准备好抢鞋子了
...................
六、线程Queue
线程和进程中的队列是两种不同的类,就像之前的Lock锁一样。同样队列也是为了解决共享变量间会出现数据不安全问题,用线程Queue通信,不需要加锁,内部自带
注意:线程中有三种队列
- Queue队列:基本队列FIFO原则,先进先出
- PriorityQueu:优先级队列,谁小谁先出
- LifoQueue:栈,LIFO原则,后进先出
# q=Queue(5)
# q.put("lqz")
# q.put("egon")
# q.put("铁蛋")
# q.put("钢弹")
# q.put("金蛋")
#
#
# # q.put("银蛋")
# # q.put_nowait("银蛋")
# # 取值
# print(q.get())
# print(q.get())
# print(q.get())
# print(q.get())
# print(q.get())
# # 卡住
# # print(q.get())
# # q.get_nowait()
# # 是否满,是否空
# print(q.full())
# print(q.empty())
# LifoQueue
# q=LifoQueue(5)
# q.put("lqz")
# q.put("egon")
# q.put("铁蛋")
# q.put("钢弹")
# q.put("金蛋")
# #
# # q.put("ddd蛋")
# print(q.get())
#PriorityQueue:数字越小,级别越高
# q=PriorityQueue(3)
# q.put((-10,'金蛋'))
# q.put((100,'银蛋'))
# q.put((101,'铁蛋'))
# # q.put((1010,'铁dd蛋')) # 不能再放了
#
# print(q.get())
# print(q.get())
# print(q.get())
七、线程池
现成的创建是需要消耗系统资源的,我们创建很多线程往往浪费了很多资源,我们可以限制执行的任务数量,每个线程分配一个任务,剩下的任务排队等候,等某个线程完成任务后,排队任务就可以安排个这个线程继续执行。简单来说就是我们有很多任务,但是我们只让几个线程来完成这些任务,限制多少个线程就是利用线程池来完成。
threading.``current_thread
():返回当前对应调用者的控制线程的 Thread
对象。如果调用者的控制线程不是利用 threading
创建,会返回一个功能受限的虚拟线程对象
import time
from threading import current_thread
from concurrent.futures import ThreadPoolExecutor
import random
pool = ThreadPoolExecutor(max_workers=3) # 设置线程池中的数量为三个
def print_data(i):
time.sleep(random.randint(1, 3))
print(f"我是线程{current_thread().getName()}我正在执行第{i}个任务")
return print(f"第{i}个任务结束")
def call_back(f):
pass
if __name__ == '__main__':
# ll=[]
# for i in range(10): # 起了10个线程
# # t=Thread(target=print_data)
# # t.start()
# res = pool.submit(task, i) # 不需要再写在args中了
# # res是Future对象 任务的句柄
# # from concurrent.futures._base import Future
# # print(type(res))
# # print(res.result()) # 像join 获取任务的结果(返回值),这个方法是阻塞的
# ll.append(res)
#
# for res in ll:
# print(res.result())
for i in range(10):
pool.submit(print_data, i + 1).add_done_callback(call_back)
# 将一个任务放入任务队列,并且start()一个线程,并让这个线程从队列取出和完成fn这个任务,args是fn任务函数的参数;返回的task是一个Future对象
---------------------------------
>>> 我是线程ThreadPoolExecutor-0_0我正在执行第1个任务
>>> 我是线程ThreadPoolExecutor-0_1我正在执行第2个任务
>>> 我是线程ThreadPoolExecutor-0_2我正在执行第3个任务
>>> 第2个任务结束
>>> 我是线程ThreadPoolExecutor-0_1我正在执行第4个任务
>>> 第3个任务结束
从上面的代码我们发现确实只有三个线程在跑,并且是跑完后再分配给原来的三个线程
基本用法:
ThreadPoolExecutor(_base.Executor)
def __init__(self, max_workers=None, thread_name_prefix='',
initializer=None, initargs=()):
max_workers:可用于执行给定调用的最大线程数。 默认5个或者CPU核心数*5
thread_name_prefix:提供线程的可选名称前缀。初始化程序:用于初始化工作线程的可调用对象。
initargs:传递给初始化程序的参数的元组。
submit(self, fn, *args, **kwargs):
使用 submit 函数来提交线程需要执行的任务到线程池中,并返回该任务的句柄(类似于文件、画图),注意 submit() 不是阻塞的,而是立即返回。
fn
需要执行的任务\函数*args, **kwarg
任务\函数的参数
class Future(object):
# Future是一个存储着异步任务的执行结果和运行状态的容器;当然Future不只是一个容器,还负责一个任务的结果获取,取消,判断是否完成,异常设置等功能。一个Future对应着一个任务(任务函数)的结果和状态。
def __init__(self):
# Future对象不能在客户端脚本实例化,只能在ThreadPoolExecutor这样的python内部代码中实例化
self._condition = threading.Condition() # 创建了一个条件变量
self._state = PENDING # 任务状态默认是“正在执行”
self._result = None # 任务结果默认为空,因为任务还没开始执行或者正在执行
self._exception = None
self._waiters = []
self._done_callbacks = []
def _invoke_callbacks(self):
for callback in self._done_callbacks:
try:
callback(self)
except Exception:
LOGGER.exception('exception calling callback for %r', self)
# 用于取消执行某一个任务
def cancel(self):
# 翻译: 不能取消一个正在执行或已执行完的任务。只能取消还未执行的任务。什么是未执行的任务?线程池ThreadPoolExecutor限定了能同时处于执行状态的线程数n,但往线程池中添加的任务数m如果超过了n,m个任务中只有n个可以同时执行,那么m-n那部分任务需要等待前面的任务执行完才能开始执行,这些就是还未执行的任务。
with self._condition:
if self._state in [RUNNING, FINISHED]:
return False
if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
return True
self._state = CANCELLED
self._condition.notify_all() # 取消任务的时候,会唤醒 get_result()中条件变量的wait(),目的是为了告诉get_result说:“不必等待已取消的任务的结果了”
self._invoke_callbacks()
return True
# 判断这个Future任务是否处于“已被取消”状态
def cancelled(self):
"""Return True if the future was cancelled."""
with self._condition:
return self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]
# 判断这个Future任务是否处于“正在运行”状态
def running(self):
"""Return True if the future is currently executing."""
with self._condition:
return self._state == RUNNING
# 判断这个Future任务是否处于“执行完毕”状态
def done(self):
"""Return True of the future was cancelled or finished executing."""
with self._condition:
return self._state in [CANCELLED, CANCELLED_AND_NOTIFIED, FINISHED]
# 获取任务函数的返回值
def __get_result(self):
if self._exception:
raise self._exception
else:
return self._result
# 添加任务函数执行后要执行的函数
def add_done_callback(self, fn):
# 翻译:该方法用于添加一个函数,这个函数会在任务函数完成或者被取消的时候执行。函数会被存放到_done_callbacks这个列表中。所以可以为一个任务函数添加多个这样的函数。这些_done_callbacks中的函数的执行顺序是按照存入列表的顺序执行的。
with self._condition:
if self._state not in [CANCELLED, CANCELLED_AND_NOTIFIED, FINISHED]:
self._done_callbacks.append(fn)
return
fn(self)
# 获取任务执行的结果,由于使用了_self.condition.wait()方法,所以是个阻塞的方法。
def result(self, timeout=None):
# 获取Future任务函数的返回值。由于要获取任务返回值,所以肯定要等任务执行完才能获取返回值,所以result()方法会阻塞等待Future对象中的任务函数执行完才能被唤醒。
with self._condition:
if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
raise CancelledError() # 如果对已取消的任务获取返回值结果会报错
elif self._state == FINISHED: # 如果对已执行完毕的任务获取结果则无需等待,直接返回结果
return self.__get_result()
self._condition.wait(timeout) # 如果对正在执行的任务或者还未执行的任务获取结果,则需要等待,等到任务结束了才能获取结果
# 此时 wait() 被唤醒,但是仍要再判断一次任务的状态,因为这个任务可能是还未执行的任务被取消了,取消任务是也会notify()唤醒wait()
if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
raise CancelledError()
elif self._state == FINISHED:
return self.__get_result()
else:
raise TimeoutError()
# 用于获取任务执行时报的异常
def exception(self, timeout=None):
with self._condition:
if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
raise CancelledError()
elif self._state == FINISHED:
return self._exception
self._condition.wait(timeout)
if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
raise CancelledError()
elif self._state == FINISHED:
return self._exception
else:
raise TimeoutError()
# 设置任务结果
def set_result(self, result):
# 这个方法只能在任务执行完毕时在线程池的线程中调用,而不能在主线程中调用
with self._condition:
self._result = result # 将任务结果存到对象属性中
self._state = FINISHED # 将任务状态改为结束
for waiter in self._waiters:
waiter.add_result(self)
self._condition.notify_all() # 唤醒和通知 result()中的wait(),说:“任务函数的结果已经拿到了,可以把结果返回给主线程了”
self._invoke_callbacks() # 执行任务函数完成后要执行的函数
def set_exception(self, exception):
with self._condition:
self._exception = exception
self._state = FINISHED
for waiter in self._waiters:
waiter.add_exception(self)
self._condition.notify_all()
self._invoke_callbacks()
进程池
# 1 如何使用
from concurrent.futures import ProcessPoolExecutor
pool = ProcessPoolExecutor(2)
pool.submit(get_pages, url).add_done_callback(call_back)