面试题之GIL全局解释器锁和互斥锁(详细后面补)
一、全局解释器锁(GIL)
什么是(GIL)全局解释器锁?详细概念
GIL(全局解释器锁)是Python解释器的一个特性。它是一种线程锁,它确保同一时刻只有一个线程可以执行Python字节码。这意味着Python解释器中的多个线程不能同时执行Python代码,而只能交替执行。
GIL的存在是为了保护Python解释器内部的数据结构免受并发修改的影响。由于Python中的一些数据结构(如内存管理器)是非线程安全的,因此如果多个线程同时修改这些结构,可能会导致数据结构出现不一致的状态,甚至导致崩溃。通过使用GIL,Python解释器可以避免这种情况发生,但是这也意味着在多线程Python程序中,只有一个线程可以执行Python字节码,其他线程必须等待。
需要注意的是,GIL只是针对Python解释器内部的线程安全问题而设计的,并不是为了防止多线程Python程序的执行。Python程序可以使用多个线程来并发执行I/O操作或调用C语言编写的扩展模块等任务,这些任务不需要GIL的保护,因此不会被GIL所限制。
1、什么是全局解释器锁
每个CPU在同一时间只能执行一个线程,那么其他的线程就必须等待该线程的全局解释器,使用权消失后才能使用全局解释器,即使多个线程直接不会相互影响在同一个进程下也只有一个线程使用cpu,这样的机制称为全局解释器锁(GIL)。
GIL的设计简化了CPython的实现,使的对象模型包括关键的内建类型,如:字典等,都是隐含的,可以并发访问的,锁住全局解释器使得比较容易的实现对多线程的支持,但也损失了多处理器主机的并行计算能力。
2、全局解释器锁的好处
-
避免了大量的加锁解锁的好处
-
使数据更加安全,解决多线程间的数据完整性和状态同步
全局解释器的缺点
多核处理器退化成单核处理器,只能并发不能并行。
4、GIL的作用:
多线程情况下必须存在资源的竞争,GIL是为了保证在解释器级别的线程唯一使用共享资源(cpu)。
什么是互斥锁?
概念
互斥锁(Mutex,互斥量)是一种用于保护共享资源(如内存、文件等)的锁,它能够确保在同一时刻只有一个线程可以访问被保护的资源,从而避免多个线程同时访问共享资源导致的数据不一致性和竞态条件等问题。
互斥锁的工作原理是,当一个线程想要访问共享资源时,它必须首先获取互斥锁,如果互斥锁已经被另一个线程持有,则该线程将被阻塞直到互斥锁被释放。当该线程完成对共享资源的访问后,它必须释放互斥锁,以允许其他线程访问共享资源。
在Python中,可以使用threading模块提供的Lock对象来实现互斥锁。使用Lock对象时,可以通过调用acquire()方法来获取锁,如果锁已经被另一个线程持有,则调用线程将被阻塞,直到锁被释放。调用release()方法可以释放锁,允许其他线程获取锁并访问共享资源。需要注意的是,在使用互斥锁时,需要小心避免死锁等问题。
代码解释
# 互斥锁:建议只加载操作数据的部分 否则整个程序的效率会极低
from multiprocessing import Process, Lock
import time
import json
import random
def search(name):
with open(r'data.json', 'r', encoding='utf8') as f:
data = json.load(f)
print('%s查看票 目前剩余:%s' % (name, data.get('ticket_num')))
def buy(name):
# 先查询票数
with open(r'data.json', 'r', encoding='utf8') as f:
data = json.load(f)
# 模拟网络延迟
time.sleep(random.randint(1, 3))
# 买票
if data.get('ticket_num') > 0:
with open(r'data.json', 'w', encoding='utf8') as f:
data['ticket_num'] -= 1
json.dump(data, f)
print('%s 买票成功' % name)
else:
print('%s 买票失败 非常可怜 没车回去了!!!' % name)
def run(name, mutex):
search(name)
mutex.acquire() # 抢锁
buy(name)
mutex.release() # 释放锁
if __name__ == '__main__':
mutex = Lock() # 产生一把锁
for i in range(10):
p = Process(target=run, args=('用户%s号' % i, mutex))
p.start()
"""
锁有很多种 但是作用都一样
行锁 表锁 ...
"""
二、同步锁
1、什么是同步锁?
同一时刻的一个进程下的一个线程只能使用一个cpu,要确保这个线程下的程序在一段时间内被cpu执,那么就要用到同步锁。
2、为什么用同步锁?
因为有可能当一个线程在使用cpu时,该线程下的程序可能会遇到io操作,那么cpu就会切到别的线程上去,这样就有可能会影响到该程序结果的完整性。
3、怎么使用同步锁?
只需要在对公共数据的操作前后加上上锁和释放锁的操作即可。
4、同步锁的所用:
为了保证解释器级别下的自己编写的程序唯一使用共享资源产生了同步锁。
三、死锁
1、什么是死锁?
指两个或两个以上的线程或进程在执行程序的过程中,因争夺资源或者程序推进顺序不当而相互等待的一个现象。
2、死锁产生的必要条件?
互斥条件、请求和保持条件、不剥夺条件、环路等待条件
3、处理死锁的基本方法?
预防死锁、避免死锁(银行家算法)、检测死锁(资源分配)、解除死锁:剥夺资源、撤销进程
了解的锁
四、递归锁
在Python中为了支持同一个线程中多次请求同一资源,Python提供了可重入锁。这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。递归锁分为可递归锁与非递归锁。
五、乐观锁
假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。
六、悲观锁
假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。
python常用的加锁方式:互斥锁、可重入锁、迭代死锁、互相调用死锁、自旋锁。
1.线程和进程:
线程是属于进程的,线程运行在进程空间内,同一进程所产生的线程共享同一内存空间,当进程退出时该进程所产生的线程都会被强制退出并清除。线程可与属于同一进程的其它线程共享进程所拥有的全部资源,但是其本身基本上不拥有系统资源,只拥有一点在运行中必不可少的信息(如程序计数器、一组寄存器和栈)。
2.线程、进程与协程:
线程和进程的操作是由程序触发系统接口,最后的执行者是系统;协程的操作则是程序员
协程存在的意义:对于多线程应用,CPU通过切片的方式来切换线程间的执行,线程切换时需要耗时(保持状态,下次继续)。协程,则只使用一个线程,在一个线程中规定某个代码块执行顺序。
协程的适用场景: 当程序中存在大量不需要CPU的操作时(IO),适用于协程;