一堆锁

死锁现象(*****

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

  • 产生死锁的情况

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

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

      解决方案:

      1. 按照相同的顺序进行抢锁
      2. 给抢锁加上超时,如果超时就放弃抢锁

递归锁 RLock (了解)

​ 与普通的互斥锁相同点在于,在多线程之间有互斥的效果但是同一个线程中,递归锁可以多次加锁,执行多次acquire

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

​ RLock只是解决了代码逻辑上的错误导致的死锁,并不能解决多个锁造成的死锁问题

  1. # 同一把RLock 多次acquire
  2. #l1 = RLock()
  3. #l2 = l1
  4. # 不同的RLock 依然会锁死
  5. #l1 = RLock()
  6. #l2 = RLock()
  7. def task():
  8. l1.acquire()
  9. print(threading.current_thread().name,"拿到了筷子")
  10. time.sleep(0.1)
  11. l2.acquire()
  12. print(threading.current_thread().name, "拿到了盘子")
  13. print("吃饭")
  14. l1.release()
  15. l2.release()
  16. def task2():
  17. l2.acquire()
  18. print(threading.current_thread().name, "拿到了盘子")
  19. l1.acquire()
  20. print(threading.current_thread().name,"拿到了筷子")
  21. print("吃饭")
  22. l2.release()
  23. l1.release()
  24. t1 = Thread(target=task)
  25. t1.start()
  26. t2 = Thread(target=task2)
  27. t2.start()

信号量 (了解)

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

​ 如果限制数量为1,则和普通的互斥锁一样

  1. from threading import Thread, Semaphore, currentThread
  2. import time
  3. s = Semaphore(3) # 一次只能有3个线程占用
  4. def task():
  5. s.acquire()
  6. time.sleep(1)
  7. print(currentThread().name)
  8. s.release()
  9. for i in range(10):
  10. Thread(target=task).start()

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

GIL(*****

什么时GIL锁

  1. '''
  2. 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.)
  3. '''
  4. '''
  5. 在CPython中,全局解释器锁(global interpreter lock, GIL)是一个互斥体,它防止多个本机线程同时执行Python字节码。这个锁是必要的,主要是因为CPython的内存管理不是线程安全的。(然而,自从GIL存在以来,其他特性已经逐渐依赖于它强制执行的保证。)
  6. '''

GIL本质是一把互斥锁,但GIL锁住的是解释器级别的数据

自定义锁,锁的是解释器以外的共享资源,例如:硬盘上的文件 控制台,对于这种不属于解释器的数据资源就应该自己加锁处理

为什么需要GIL锁

​ python程序本身只是一串字符,只有通过python解释器运行之后才具有意义,但是在一个python程序中python解释器只有一个,所有代码都是由它来执行的,当多个线程都需要执行代码时,就会产生线程安全问题。

​ 如果我们不开线程,是否就没有问题了?

Cpython解释器与GC的问题

GC线程又叫垃圾回收机制

在python中,解释器会自动帮我们进行垃圾回收,这也是需要开启一个线程来进行的,也就是说,当我们没有开启子线程的时候,内部还是有多个线程在执行。所以这就会带来线程安全问题

例如:

​ 线程a要定义一个变量 b,首先申请一块空内存,然后把数据装进去,此时变量b的引用计数为0,最后才会将引用计数加一

​ 但是,如果在刚把数据装进去的时候,CPU切换到GC线程,GC线程就会把变量b当作垃圾进行回收

GIL锁带来的问题

GIL锁本质还是一把互斥锁,所以就会降低程序运行效率

  • 没有解决办法,只有尽可能的避免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. 如果没有这把锁,我们程序猿将必须自己来解决安全问题

性能测试 :

  1. 计算密集型任务

    1. from multiprocessing import Process
    2. from threading import Thread
    3. import time
    4. # # 计算密集型任务
    5. def task():
    6. for i in range(100000000):
    7. 1+1
    8. if __name__ == '__main__':
    9. start_time = time.time()
    10. ps = []
    11. for i in range(5):
    12. # p = Process(target=task) # 4.420854806900024
    13. p = Thread(target=task) # 20.043513298034668
    14. p.start()
    15. ps.append(p)
    16. for i in ps:i.join()
    17. print("共耗时:",time.time()-start_time)
  2. IO密集型

    1. from multiprocessing import Process
    2. from threading import Thread
    3. import time
    4. # IO密集型任务
    5. def task():
    6. for i in range(100):
    7. with open(r"1.死锁现象.py", 'r', encoding="utf-8") as fr:
    8. fr.read()
    9. if __name__ == '__main__':
    10. start_time = time.time()
    11. ps = []
    12. for i in range(10):
    13. # p = Process(target=task) # 1.1040928363800049
    14. p = Thread(target=task) # 0.09919905662536621
    15. p.start()
    16. ps.append(p)
    17. for i in ps:i.join()
    18. print("共耗时:",time.time()-start_time)

进程池与线程池

池是一个容器,可以用来装一些东西,但池也是有容量限制的,不可能无限制的装东西

进程池和线程池就是用来装进程和线程的容器

进程池和线程池的好处:

  1. 可以避免频繁的创建和销毁(进程/线程)来的资源开销
  2. 可以限制同时存在的线程数量,以保证服务器不会应为资源不足而导致崩溃
  3. 帮我们管理了线程的生命周期
  4. 管理了任务的分配

进程池与线程池的使用

  1. 线程池

    1. from concurrent.futures import ThreadPoolExecutor
    2. from threading import enumerate,currentThread
    3. # 创建一个线程池 指定最多可以容纳两个线程
    4. pool = ThreadPoolExecutor(2)
    5. def task():
    6. print(currentThread().name)
    7. # 提交任务到池子中
    8. pool.submit(task)
    9. pool.submit(task)
    10. print(enumerate())
  2. 进程池

    1. import os
    2. import time
    3. from concurrent.futures import ProcessPoolExecutor
    4. # 创建一个进程池, 指定最多可以容纳两个线程
    5. def task():
    6. time.sleep(1)
    7. print(os.getpid())
    8. if __name__ == '__main__':
    9. pool = ProcessPoolExecutor(2)
    10. pool.submit(task)
    11. pool.submit(task)
    12. pool.submit(task)

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

同步异步(*****

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

同步:是指提交任务后,必须需要等待任务完成之后才能进行下一个任务

异步:是指提交任务后,不需要等待任务完成,继续进行下一个任务

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

解决办法:

  1. 轮询:每隔一段时间询问一次

    ​ 结果:效率低,无法及时获取结果

  2. 异步回调:让任务的执行方主动通知

    ​ 结果:可以及时拿到任务的结果

案例:

  1. from threading import Thread
  2. # 具体的任务
  3. def task(callback):
  4. print('run')
  5. for i in range(100000000):
  6. 1 + 1
  7. callback(True)
  8. # 回调函数,参数为任务的结果
  9. def finished(res):
  10. if res:
  11. print('任务完成')
  12. else:
  13. print('任务失败')
  14. print('start...')
  15. t = Thread(target=task,args=(finished,))
  16. t.start()
  17. print('over')

​ 线程池中回调的使用

  1. from concurrent.futures import ThreadPoolExecutor
  2. def task(num):
  3. time.sleep(1)
  4. print(num)
  5. return "hello python"
  6. def callback(obj):
  7. print(obj.result())
  8. pool = ThreadPoolExecutor()
  9. res = pool.submit(task,123)
  10. res.add_done_callback(callback)
  11. print("over")

Event事件

Event用于线程间状态同步,

执行状态:指的是程序运行到哪一步,此时的状态

执行结果:程序运行完了得到的结果

如果我们要拿到执行结果,可以采用异步回调

Event事件的本质就是一个标志,True or False

Evevt里面包含了一个wait函数,可以阻塞当前线程,直到状态由False变为True

  1. from threading import Thread, Event
  2. import time
  3. e = Event()
  4. # is_bool = False
  5. def start_server():
  6. # global is_bool
  7. print('starting server.....')
  8. time.sleep(3)
  9. print('server started')
  10. # is_bool = True
  11. e.set()
  12. def connect_server():
  13. e.wait()
  14. if e.is_set():
  15. print('连接服务器成功')
  16. # while True:
  17. # if is_bool:
  18. # print('连接服务器成功')
  19. # break
  20. # else:
  21. # print('连接服务器失败')
  22. #
  23. # time.sleep(0.1)
  24. t1 = Thread(target=start_server)
  25. t2 = Thread(target=connect_server)
  26. t1.start()
  27. t2.start()

并发编程--一堆锁,GIL,同步异步,Event事件的更多相关文章

  1. Python之路(第三十六篇)并发编程:进程、同步异步、阻塞非阻塞

    一.理论基础 进程的概念起源于操作系统,是操作系统最核心的概念,也是操作系统提供的最古老也是最重要的抽象概念之一.操作系统的其他所有内容都是围绕进程的概念展开的. 即使可以利用的cpu只有一个(早期的 ...

  2. 并发编程(五)——GIL全局解释器锁、死锁现象与递归锁、信号量、Event事件、线程queue

    GIL.死锁现象与递归锁.信号量.Event事件.线程queue 一.GIL全局解释器锁 1.什么是全局解释器锁 GIL本质就是一把互斥锁,相当于执行权限,每个进程内都会存在一把GIL,同一进程内的多 ...

  3. Java并发编程:锁的释放

    Java并发编程:锁的释放 */--> code {color: #FF0000} pre.src {background-color: #002b36; color: #839496;} Ja ...

  4. Java并发编程:线程的同步

    Java并发编程:线程的同步 */--> code {color: #FF0000} pre.src {background-color: #002b36; color: #839496;} J ...

  5. Java并发编程的4个同步辅助类

    Java并发编程的4个同步辅助类(CountDownLatch.CyclicBarrier.Semphore.Phaser) @https://www.cnblogs.com/lizhangyong/ ...

  6. 并发编程(五)--GIL、死锁现象与递归锁、信号量、Event事件、线程queue

    一.GIL全局解释器锁 1.什么是全局解释器锁 GIL本质就是一把互斥锁,相当于执行权限,每个进程内都会存在一把GIL,同一进程内的多个线程,必须抢到GIL之后才能使用Cpython解释器来执行自己的 ...

  7. Day12- Python基础12 线程、GIL、Lock锁、RLock锁、Semaphore锁、同步条件event

    http://www.cnblogs.com/yuanchenqi/articles/6248025.html  博客地址 本节内容: 1:进程和线程的说明 2:线程的两种调用方式 3:threadi ...

  8. 1.gil全局解释器锁, 2. 死锁与递归锁 3. 信号量 4. Event事件 5. 线程queue

    gil本质就是一把互斥锁,相当于执行权限,每个进程都会存在一把gil,同一进程内的多个线程必须抢到gil 之后才能使用cpython解释器来执行自己的代码,同一进程下的多线程不能并行,但可以实现并发 ...

  9. python并发编程之IO模型 同步 异步 阻塞 非阻塞

    IO浅谈 首先 我们在谈及IO模型的时候,就必须要引入一个“操作系统”级别的调度者-系统内核(kernel),而阻塞非阻塞是跟进程/线程严密相关的,而进程/线程又是依赖于操作系统存在的,所以自然不能脱 ...

随机推荐

  1. Hello,DTOS!(上)

    主引导程序是软件还是固件?如果是软件,那么由谁开发?如何开发?主引导程序是软件.因为它不是固化于硬件当中的,并不是在出厂之前已经烧到硬件里面去了.因此它必然是软件.既然是软件,那是谁来开发它呢?就目前 ...

  2. Latex使用过程中的一些总结

    本文主要总结在使用Latex过程中遇到的一些问题及解决方案. 一:关于参考文献 1.如何在paper同一处用\cite命令同时引用多篇文献? 用\cite{bibtex1}\cite{bibtex2} ...

  3. 用bitSet做百万级ip去重

    如果直接将几百万数据仍到bitset,内存是否够用?实际测试,600万ip放到一个bitSet中,jvm内存会爆. 所以,就简单做了下分组,构建一个HashMap<String, BitSet& ...

  4. java注释代码规范

    //收集了一小部分,忘记的时候过来查一下 java--hadoop部分 /** * 此类用来处理DNS原始日志:统计给定域名平均响应时延 * @param Input * @param Output ...

  5. ABP 菜单和权限

    大致操作步骤,原理之后补充. 添加菜单: 在 ContractOwner.Web.Startup.ContractOwnerNavigationProvider 的SetNavigation方法中添加 ...

  6. 4-OpenResty 配置 https 访问

    首先是下载证书  https://www.cnblogs.com/yangfengwu/p/11809757.html 因为咱用的 Nginx 所以 修改这个 server { listen ssl; ...

  7. Debian9下安装Python3 pip

    Debian9下安装Python3 pip 使用apt-get安装Python3-pip包 apt-get install python3-pip

  8. 洛谷p3353在你窗外闪耀的星星题解

    题目 首先被题目甜到了 本来搜标签搜的线段树,结果发现这题目很吸引我我果断点开 觉得前缀和就能A啊 于是乎 要注意 窗户旁边是可以看到的 所以前缀和的时候是不用再-1的 //前缀和 //注意坑点 // ...

  9. 神经机器翻译(seq2seq RNN)实现详解

    http://c.biancheng.net/view/1947.html seq2seq 是一类特殊的 RNN,在机器翻译.文本自动摘要和语音识别中有着成功的应用.本节中,我们将讨论如何实现神经机器 ...

  10. Hadoop Capacity调度器概念及配置

    在Yarn框架中,调度器是一块很重要的内容.有了合适的调度规则,就可以保证多个应用可以在同一时间有条不紊的工作.最原始的调度规则就是FIFO,即按照用户提交任务的时间来决定哪个任务先执行,但是这样很可 ...