死锁现象,递归所,信号量,GIL

各式各样的锁

死锁现象

  • 死锁指的是某个资源被占用后一直得不到释放,导致其他需要这个资源的线程进入阻塞状态
  • 产生死锁的情况:
    • 第一种:对同一把互斥锁,加锁了多次
      • 这么做没有意义,是因为忘了之前上锁了
      • 可以给acquire加上超时参数,能够保证线程不被卡死
    • 第二种:一个共享资源有多把锁,被不同的进程或是线程所持有,导致双方互相等待对方释放,从而卡死程序
      • 抢锁一定要按照相同的顺序去抢:都抢筷子或者都抢碗
      • 加上超时参数

递归锁

  • 与普通锁的区别:

    • 多线程之间都有互斥的效果

    • 不同在于,同一个线程可以对这个锁执行多次acquire,那就是锁多次,需要用同等次数的release 才可以解开

信号量

  • 可以限制同时并发执行公共代码的线程数量
  • 如果限制数量为1,则与普通互斥锁没有区别
  • 不是用来解决安全问题的,而是用来限制并发峰值的,但是没什么用,因为这是开了线程只是不让执行,而不是没开线程
from threading import Semaphore,currentThread,Thread
import time

s = Semaphore(3)	# 有三把锁

def task():
    s.acquire()
    time.sleep(1)
    print(currentThread().name)
    s.release()

for i in range(6):
    Thread(target=task).start()
Thread-1
Thread-3
Thread-2
Thread-4
Thread-5
Thread-6

虽然可以三个三个的输出,但是顺序无法控制

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会自动帮我们处理垃圾,清扫垃圾也是一堆代码,也需要开启一个线程来执行

也就是说,就算程序没有自行开启线程,内部也有多个线程,只是Python帮我们封装了

而GC线程与我们程序中的线程就会产生安全问题

例如: 线程A要定义一个变量

步骤: 先申请一块空的内存,再把数据装进去,最后引用计数加1,
	
**但是如果当你进行到第二步的时候,CPU切换到了GC线程,GC就会把这个值当成垃圾清理**

带来的问题

GIL是一把互斥锁,就会导致效率降低

具体表现是,在CPython中,即便开启了多线程,而且CPU也是多核的,但是依旧无法并行执行任务

因为解释器只有一个,同一时间只能有一个任务在执行

如何解决

没办法解决,因为有一部分历史遗留问题,只能尽可能的避免GIL锁带来的影响

  1. 使用多进程能够实现并行,从而更好的利用多核CPU

  2. 对任务进行区分

这类任务又可以分为两类
	1. 计算密集型:基本没有IO,大部分时间都在计算。例如:人脸识别,图像处理
        1.1. 由于多线程不能并行,应该使用多进程,将任务分给不同的CPU核心
           		
	2. IO密集型:基本没有计算,大部分时间都在IO操作
		2.1. 虽然并发执行慢,但是由于网络IO速度相对于CPU的处理速度来说非常慢,多线程并不会造成太大的效率影响
		2.2. 另外如果有大量的客户端连接服务器,进程根本开不起来,只能使用多线程。

以做饭洗衣为例

关于性能的讨论

之所以加锁是为了解决线程安全问题

由于有了锁,导致Cpython中多线程不能并行只能并发
但是我们不能够因此否认Python
1. Python是一门语言,而GIL是CPython解释器的问题,还有JPython解释器,PyPy解释器
2,如果是单核CPU,就不会造成任何影响
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)

# 量小时多线程胜,量大时多进程胜,总体多线程胜,因为计算量不会小


# IO密集型任务
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)

# 多线程胜

GIL与自定义锁的区别

GIL锁住的是解释器级别的数据,而自定义锁锁的是解释器以外的共享资源,例如:硬盘上的文件,控制台

代码是为了模拟,强行加了锁,##GIL锁在遇到阻塞时会释放,lock锁不会##

虽然没有在代码中有实际作用,但是理解这个有助于更高效率处理问题

原文地址:https://www.cnblogs.com/lucky75/p/11140598.html