锁-GIL-同步异步-event
Posted zhuyuanying123--
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了锁-GIL-同步异步-event相关的知识,希望对你有一定的参考价值。
一堆锁
死锁
对同一把互斥锁多次执行acquire 将导致死锁------>资源被占用一直得不到释放,导致其他资源进入阻塞状况
产生死锁的情况:
? 1:对同一把互斥锁,枷锁了多次
? 2:一个共享资源要访问必须具备多把锁,但是这些锁被不同线程或进程持有,就会导致相互等待对方释放资源,从而程序卡死
解决情况:
? 1:抢锁,按照相同循序去抢
? 2:给抢锁,加上超时,若超时则放弃执行
给acquire加上超时,可以保证线程不会卡死
l=Lock()
l.acquire()
l.acquire(timeout=3)
递归锁(了解)
其实和普通锁区别
相同点:多线程之间有互斥效果
不同点:同一线程可以对这个锁执行多次acquire
同一线程必须保证,加锁的次数和解锁的次数相同,其他线程才能抢到这把锁
信号量(了解)
可以限制同时并发执行公共代码的线程数量
如果限制数量为一,则和普通锁没有区别
from threading import Semaphore ,currentThread,Thread
import time
s= Semaphore(5)
def task():
s.acquire()
time.sleep(1)
print(currentThread().name)
s.release()
for i in range(10):
Thread(target=task).start()
Thread-1
Thread-2
Thread-3
Thread-5
Thread-4
Thread-6
Thread-7
Thread-8
Thread-9
Thread-10
信号量不是用来解决安全问题的,而是用于限制最大的并发量
GIL
什么是GIL
"""In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple
native threads from executing Python bytecodes at once. This lock is necessary mainly
because CPython’s memory management is not thread-safe. (However, since the GIL
exists, other features have grown to depend on the guarantees that it enforces.
"""
"""
在CPython中,全局解释器锁(GIL)是一个防止多个互斥锁的互斥锁
一次执行Python字节码的本机线程。这把锁主要是必要的
因为CPython的内存管理不是线程安全的。(然而,自从吉尔
在现有的基础上,其他功能已经逐渐依赖于它所实施的保证。
"""
为何用这把锁
线程安全问题具体的表现
cpython 解释器 与python程序之间联系
python程序的本质就是一堆字符串,所提运行一个python程序时,必须要开启一个解释器
但是在一个python程序中解释器只有一个,所有代码都要交给他来执行
但是当多个线程都要执行代码时就会产生线程安全问题
cpython的解释器与Gc的问题
python会自动帮我们处理垃圾,清扫垃圾,清扫垃圾也是一对代码,也需要开启一个线程来执行
换句话说就算程序没有开启线程,内部也有多个线程
GC线程与我们程序中的线程就会产生安全问题
申请一块空的内存,再把数据装进去,最后把引用计数加1
如果进行到第二步时候,cpu切换到了GC线程,Gc就会把这个只当作垃圾清理掉
带来的问题
Gil是一把互斥锁,互斥锁将导致效率降低
具体表现在 CPython即便开启了多线程,而且CPU也是多核的,却无法并行执行任务
因为解释器就一个,同一时间只有一个任务在执行
解决方案
么有办法解决,只能避免GLC锁影响我们的效率
1:使用多进程能够实现并行,从而更好地利用多核Cpu
2:对任务进行区分
? 分为两类:
? 1计算密集型 基本没有io 大部分时间都在计算,例如人脸识别,图像处理
? 由于多线程不能并行,应该使用多进程,将任务分给不同的CPU核心
? 2io密集型 计算任务非常少的,大部分时间都在等待IO操作
? 由于网络IO速度对比cpu处理速度非常慢,多线程并不会造成太大影想
? 另外如有大量的客户端链接服务,进程根本开不起来,只能用多线程
关于性能的讨论
加锁是为了解决线程的安全问题
再来,由于有了锁,导致CPython中的多线程不能并行只能并发
1,python是一门语言,GIl时Cpython解释器的问题,还有jpython pypy
2,如果是单核cpu,gil不会造成任何影响
3,由于目前大多数程序是基于网络的,网络的速度对比cpu是非常慢的,导致即使多核cpu也无法提高效率
4,对于io密集型任务,不会有太大的影响
5,如果没有这把锁,程序员之恩自己解决安全问题
from multiprocessing import Process
from threading import Thread
import time
# # 计算密集型任务
#
# def task():
# for i in range(100000000):
# 1+1
#
#
# if __name__ == '__main__':
# start_time = time.time()
#
# ps = []
# for i in range(5):
# p = Process(target=task)
# # p = Thread(target=task)
# p.start()
# ps.append(p)
#
# for i in ps:i.join()
#
# print("共耗时:",time.time()-start_time)
# 多进程胜
def task():
for i in range(100):
with open(r"1.死锁现象.py",encoding="utf-8") as f:
f.read()
if __name__ == '__main__':
start_time = time.time()
ps = []
for i in range(10):
p = Process(target=task)
# p = Thread(target=task)
p.start()
ps.append(p)
for i in ps:i.join()
print("共耗时:",time.time()-start_time)
共耗时: 0.05649399757385254
GIL与自定义锁的区别
GIL锁住的是解释器级别的数据
自定义锁,锁的是解释器以外的共享资源,例如硬盘上的文件,控制台
对于这种不属于解释器的数据资源就是应该给自己枷锁处理
线程池与进程池
池表示容器
线程就是装线程的容器
为什么要装在容器当中呢?
1,可以避免频繁的创建和销毁(进程/线程)的资源的开销
2,可以限制同时存在的线程数量,以保证服务器不会因为资源不足导致奔溃
3,帮忙管理了线程的生命周期
4,管理了任务的分配
import os
import time
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
from threading import activeCount,enumerate,currentThread
# 创建一个线程池 指定最多可以容纳两个线程
pool = ThreadPoolExecutor(20)
def task():
print(currentThread().name)
# 提交任务到池子中
pool.submit(task)
pool.submit(task)
print(enumerate())
ThreadPoolExecutor-0_0
ThreadPoolExecutor-0_0
[<_MainThread(MainThread, started 4667078080)>, <Thread(ThreadPoolExecutor-0_0, started daemon 123145511550976)>, <Thread(ThreadPoolExecutor-0_1, started daemon 123145516806144)>]
#进程池使用
def task():
time.sleep(1)
print(os.getpid())
if __name__ == '__main__':
pool = ProcessPoolExecutor(2)
pool.submit(task)
pool.submit(task)
pool.submit(task)
2365
2366
2365
若进程不结束,池子里面的进程或线程 也一直是活的
同步 异步——————
程序的状态》》》 阻塞非阻塞
处理任务的方式》》》 并发 并行 串行
同步:提交任务后必须在原地等待,知道任务结束
异步:提交任务不需要在原地等待,可以继续往下执行代码
? 异步的效率高于同步,异步任务将导致一个问题,---任务的发起方不知道任务何时处理完毕
异步同步指的是提交任务的方式
解决方案:
1轮番询问:重复一段时间问一次---->效率低,无法及时获取结果
2让任务的执行方主动通知(异步回调)---->可以及时拿到任务的结果(推荐)
异步回调使用案例:
# 异步回调
from threading import Thread
# 具体的任务
def task(callback):
print("run")
for i in range(100000000):
1+1
callback("ok")
#回调函数 参数为任务的结果
def finished(res):
print("任务完成!",res)
print("start")
t = Thread(target=task,args=(finished,))
t.start() #执行task时 没有导致主线程卡主 而是继续运行
print("over")
线程池中的回调使用:
# 使用案例:
def task(num):
time.sleep(1)
print(num)
return "hello python"
def callback(obj):
print(obj.result())
pool = ThreadPoolExecutor()
res = pool.submit(task,123)
res.add_done_callback(callback)
print("over")
event事件
线程间状态同步
你把一个任务丢到了子线程中,这个任务将异步执行,如何得到这个任务的执行状态
执行状态和执行结果不是一个概念
如果需要拿到,执行结果可以采用异步回调
假设
一个线程 负责启动服务器 启动服务器需要花一定的时间
另一个线程作为客户端 要连接服务器 必须保证服务器已经启动
要获取状态可以采永轮训的方法 但是浪费了CPU资源 而且可能会造成延迟 不能立即获取状态
就可以使用事件来完成状态同步
事件本质就是 一个标志 可以是False 或是True
特殊之处在于 其包含一个wait函数 可以阻塞当前线程 直到状态从False变为True
from threading import Thread,Event
import time
# is_boot = False
e = Event()
def start_server():
# global is_boot
print("starting server......")
time.sleep(3)
print("server started!")
# is_boot = True
# 修改事件的值为True
e.set()
def connect_server():
e.wait() # 等待事件从False 变为true
if e.is_set():
print("连接服务器成功!")
# while True:
# if is_boot:
# print("连接服务器成功!")
# break
# else:
# print("失败 服务器未启动!")
# time.sleep(0.5)
t1 = Thread(target=start_server)
t2 = Thread(target=connect_server)
t1.start()
t2.start()
starting server......
server started!
连接服务器成功!
以上是关于锁-GIL-同步异步-event的主要内容,如果未能解决你的问题,请参考以下文章