死锁 GIL 自定义锁 同步异步

1.一堆锁

死锁现象 ****

死锁指的是 某个资源被占用后一直得不到释放 导致其他需要这个资源的线程进入阻塞状态

产生死锁的情况:

1.对同一把互斥锁 加锁了多次

2.一个共享资源 要访问必须同时具备多把锁,但是这些锁被不同线程或进程所持有 , 就会导致相互等待对方释放 从而程序就卡死了

第二种的解决方法:

2.1抢锁 一顶按照相同的顺序去抢

2.1给抢锁 加上超时 如果超时则放弃执行

递归锁 了解

与普通锁的区别

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

不同在于 同一个线程可以对这个锁 执行多次acquire

同一个线程必须保证 加锁的次数和解锁的次数相同 其它线程才能够抢到这把锁

 

信号量 了解

可以限制同时并发执行公共代码的线程数量

如果限制数量为1 则与普通互斥锁没有区别

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()

注意:信号量不是用来解决安全问题的 而是用于限制最大的并发量

 

 

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线程与我们程序中的线程就会产生安全问题

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

步骤: 先申请一块空的内存 在把数据装进去 最后将引用计数加1

如果在进行到第二步的时候 CPU 切换到了GC线程 GC就会把这个值当成垃圾 清理掉

 

带来的问题

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

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

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

 

如何解决

没办法解决,只能尽可能的避免GIL锁影响我们的效率

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

2.对任务进行区分

任务可以分为两类

1.计算密集型 基本没有IO 大部分时间都在计算 例如人脸识别 图像处理

由于多线程不能并行 应该使用多进程 将任务分给不同CPU核心

2.IO密集型 计算任务非常少,大部分时间都在等待IO操作

由于网络IO速度对比CPU处理速度非常慢, 多线程并不会造成太大的影响

另外如有大量客户端连接服务 进程根本开不起来 只能用多线程

 

关于性能的讨论

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

由于有了锁 导致Cpython中多线程不能并行只能并发

但是我们不能因此否认python

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)

# 多进程胜


# 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锁 你应该知道 你的任务是什么类型 应该使用什么方式来处理 才能尽可能提高效率

 

 

 

线程池与进程池

池表示容器

线程就是装线程的容器

为什么要装到容器中

  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())

    # 进程池的使用

    def task():
       time.sleep(1)
       print(os.getpid())


    if __name__ == '__main__':
       pool = ProcessPoolExecutor(2)
       pool.submit(task)
       pool.submit(task)
       pool.submit(task)

如果进程不结束 池子里面的进程或线程 也是一直存活的

 

同步异步 *****

阻塞 非阻塞 程序的状态

并行 并发 串行 处理任务的方式

同步

指的是 提交任务后必须在原地等待 直到任务结束 ===阻塞????

异步

提交任务后不需要在原地等待 可以继续往下执行代码

异步效率高于同步 ,异步任务将导致一个问题 就是 任务的发起方不知道任务何时 处理完毕

异步同步指的是提交任务的方式

 

解决方法:

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()

 

 

 

 

 

 

 

 

小结

死锁现象

抢锁按顺序

或加超时时间 放弃抢锁

 

递归锁

信号量

限制这个资源可以被多少个线程并发访问

 

 

GIL

全局解释器锁 是一把互斥锁 加在解释器身上

因为Cpython的内存管理是非线程安全的

加锁解决了安全问题,但是导致了 Cpython下多线程 不能并行执行 只能并发执行

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

原文地址:https://www.cnblogs.com/zrx19960128/p/11145169.html