day36

死锁现象与递归锁

死锁现象

是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,如下就是死锁

  1. from threading import Thread
  2. from threading import Lock
  3. import time
  4. lock_A = Lock()
  5. lock_B = Lock()
  6. class MyThread(Thread):
  7. def run(self):
  8. self.f1()
  9. self.f2()
  10. def f1(self):
  11. lock_A.acquire()
  12. print(f"{self.name}拿到了A锁")
  13. lock_B.acquire()
  14. print(f"{self.name}拿到了B锁")
  15. lock_B.release()
  16. lock_A.release()
  17. def f2(self):
  18. lock_B.acquire()
  19. print(f"{self.name}拿到了B锁")
  20. time.sleep(0.1)
  21. lock_A.acquire()
  22. print(f"{self.name}拿到了A锁")
  23. lock_A.release()
  24. lock_B.release()
  25. if __name__ == '__main__':
  26. for i in range(3):
  27. t = MyThread()
  28. t.start()
  29. 结果:
  30. Thread-1拿到了A
  31. Thread-1拿到了B
  32. Thread-1拿到了B
  33. Thread-2拿到了A
  34. 未结束
递归锁

递归锁可以解决死锁现象,业务需要多个锁时,先要考虑递归锁

递归锁有一个计数的功能,原数字为0,上一次锁计数+1,释放一次锁计数-1

只要递归锁上面的数字不为零,其他线程就不能枪锁

总结定义:RLock,同一把锁,引用一次计数+1,释放一次计数-1,只要计数不为零,其他线程进程就抢不到,他能解决死锁问题

  1. from threading import Thread
  2. from threading import RLock
  3. import time
  4. lock_B = lock_A = RLock()
  5. class MyThread(Thread):
  6. def run(self):
  7. self.f1()
  8. self.f2()
  9. def f1(self):
  10. lock_A.acquire()
  11. print(f"{self.name}拿到了A锁")
  12. lock_B.acquire()
  13. print(f"{self.name}拿到了B锁")
  14. lock_B.release()
  15. lock_A.release()
  16. def f2(self):
  17. lock_B.acquire()
  18. print(f"{self.name}拿到了B锁")
  19. time.sleep(0.1)
  20. lock_A.acquire()
  21. print(f"{self.name}拿到了A锁")
  22. lock_A.release()
  23. lock_B.release()
  24. if __name__ == '__main__':
  25. for i in range(10):
  26. t = MyThread()
  27. t.start()

信号量

也是一种锁,控制并发数量

总结定义:同一时刻可以设置抢锁的线程或者进程数量

同进程的一样

Semaphore管理一个内置的计数器,

每当调用acquire()时内置计数器-1;

调用release() 时内置计数器+1;

计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()

实例:(同时只有5个线程可以获得semaphore,即可以限制最大连接数为5):

  1. from threading import Thread, Semaphore, current_thread
  2. import time
  3. import random
  4. sem = Semaphore(5)
  5. def task():
  6. sem.acquire()
  7. ·
  8. print(f"{current_thread().name} 厕所ing")
  9. time.sleep(random.randint(1, 3))
  10. sem.release()
  11. if __name__ == '__main__':
  12. for i in range(20):
  13. t = Thread(target=task)
  14. t.start()

GIL全局解释器锁

总结定义:全局解释器锁,同一时刻只能一个线程进入解释器,Cpython解释器具有的。

好多自称大神的说,GIL锁是python的致命缺陷,python不能多核,并发不行等等。。。。

理论上来说:单个进程的多线程可以利用多核

但是开发Cpython解释器的程序员,给解释器加了锁

为什么加锁?

1、当时都是单核时代,而且cpu价格非常贵

2、如果不加全局解释器锁,开发Cpython解释器的程序员就会在源码内部各种主动加锁,解锁,非常麻烦,各种死锁现象等等,他为了省事就直接给解释器加了一个锁

  • 优点:保证了Cpython解释器的数据资源的安全
  • 缺点:单个进程的多线程不能利用多核

Jpython没有GIL锁

pypy也没有GIL锁

现在多核时代,我将Cpython的GIL去掉行不?

​ 因为Cpython解释器所有的业务逻辑都是围绕着单个线程实现的,去掉这个GIL锁,几乎不可能

单个进程的多线程可以并发,但是不能利用多核进行并行

多个进程可以并发,并行

IO密集型

计算密集型

GIL与lock锁的区别

相同点

都是同种锁,互斥锁

不同点
  • GIL锁是全局解释器锁,保护解释器内部的资源数据的安全
  • GIL锁,上锁,释放无需手动操作
  • 自己代码中定义的互斥锁保护进程中的资源数据的安全
  • 自己定义的互斥锁必须自己手动上锁,释放锁

验证计算密集型IO密集型的效率

计算密集型

单个进程的多线程并发 vs 多个进程的并发并行

总结:计算密集型:多进程的并发并行效率高

  1. from threading import Thread
  2. from multiprocessing import Process
  3. import time
  4. import random
  5. def task():
  6. count = 0
  7. for i in range(10000000):
  8. count += 1
  9. if __name__ == '__main__':
  10. # 多进程的并发,并行
  11. start_time = time.time()
  12. l1 = []
  13. for i in range(4):
  14. p = Process(target=task)
  15. l1.append(p)
  16. p.start()
  17. for j in l1:
  18. j.join()
  19. print(f"执行效率:{time.time() - start_time}") # 1.5881953239440918
  20. # 多线程的并发
  21. start_time = time.time()
  22. l1 = []
  23. for i in range(4):
  24. p = Thread(target=task)
  25. l1.append(p)
  26. p.start()
  27. for j in l1:
  28. j.join()
  29. print(f"执行效率:{time.time() - start_time}") # 5.415819883346558
IO密集型

IO密集型:单个进程的多线程并发 vs 多个进程的并发进行

对于IO密集型:单个进程的多线程的并发效率高

  1. from threading import Thread
  2. from multiprocessing import Process
  3. import time
  4. import random
  5. def task():
  6. count = 0
  7. time.sleep(random.randint(1, 3))
  8. count += 1
  9. if __name__ == '__main__':
  10. # 多进程的并发,并行
  11. start_time = time.time()
  12. l1 = []
  13. for i in range(50):
  14. p = Process(target=task)
  15. l1.append(p)
  16. p.start()
  17. for j in l1:
  18. j.join()
  19. print(f"执行效率:{time.time() - start_time}") # 4.230581283569336
  20. # 多线程的并发
  21. start_time = time.time()
  22. l1 = []
  23. for i in range(50):
  24. p = Thread(target=task)
  25. l1.append(p)
  26. p.start()
  27. for j in l1:
  28. j.join()
  29. print(f"执行效率:{time.time() - start_time}") # 3.011176347732544

多线程实现socket通信

server
  1. import socket
  2. from threading import Thread
  3. def _accept():
  4. server = socket.socket()
  5. server.bind(("127.0.0.1", 8848))
  6. server.listen(5)
  7. while 1:
  8. conn, addr = server.accept()
  9. t = Thread(target=communicate, args=(conn, addr))
  10. t.start()
  11. def communicate(conn, addr):
  12. while 1:
  13. try:
  14. from_client_data = conn.recv(1024)
  15. print(f"来自客户端{addr[1]}的消息:{from_client_data.decode('utf-8')}")
  16. to_client_data = input(">>>").strip()
  17. conn.send(to_client_data.encode("utf-8"))
  18. except Exception:
  19. break
  20. conn.close()
  21. if __name__ == '__main__':
  22. _accept()
client
  1. import socket
  2. client = socket.socket()
  3. client.connect(("127.0.0.1", 8848))
  4. while 1:
  5. try:
  6. to_server_data = input(">>>").strip()
  7. client.send(to_server_data.encode("utf-8"))
  8. from_server_data = client.recv(1024)
  9. print(f"来自服务端的消息:{from_server_data.decode('utf-8')}")
  10. except Exception:
  11. break
  12. client.close()

进程池、线程池

无论是多线程还是多进程,如果按照上面的写法,来一个客户端请求,我就开一个线程,来一个请求开一个线程

应该是这样:你的计算机允许范围内,开启的线程进程数量越多越好

线程池:一个容器,这个容器限制住你开启线程的数量,比如4个,第一次肯定只能并发的处理4个任务,只要有任务完成,线程马上就会接着执行下一个任务

进程池:一个容器,这个容器限制住你开启进程的数量,比如4个,第一次并行的处理4个任务,只要有任务完成,进程马上就会接着执行下一个任务

  1. from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
  2. import os
  3. import time
  4. import random
  5. # print(os.cpu_count())
  6. def task(n):
  7. print(f"{os.getpid()}接客")
  8. time.sleep(random.randint(1, 3))
  9. if __name__ == '__main__':
  10. # 开启进程池(并行+并发)
  11. p = ProcessPoolExecutor(4) # 默认不写,进程池里面的进程数与cpu里面的内核个数相等
  12. #
  13. # # p.submit(task,1)
  14. # # p.submit(task,1)
  15. # # p.submit(task,1)
  16. # # p.submit(task,1)
  17. # # p.submit(task,1)
  18. # # p.submit(task,1)
  19. for i in range(22):
  20. p.submit(task, 1)
  21. # 开启线程池 (并发)
  22. # t = ThreadPoolExecutor() # 默认不写,cpu内核个数*5=线程数
  23. t = ThreadPoolExecutor(8) # 100个线程
  24. for i in range(50):
  25. t.submit(task, i)

day36——死锁、递归锁、信号量、GIL、多线程实现socket通信、线程池和进程池的更多相关文章

  1. python并发编程-多线程实现服务端并发-GIL全局解释器锁-验证python多线程是否有用-死锁-递归锁-信号量-Event事件-线程结合队列-03

    目录 结合多线程实现服务端并发(不用socketserver模块) 服务端代码 客户端代码 CIL全局解释器锁****** 可能被问到的两个判断 与普通互斥锁的区别 验证python的多线程是否有用需 ...

  2. 并发编程---死锁||递归锁---信号量---Event事件---定时器

    死锁 互斥锁:Lock(),互斥锁只能acquire一次 递归锁:  RLock(),可以连续acquire多次,每acquire一次计数器+1,只有计数为0时,才能被抢到acquire # 死锁 f ...

  3. GIL全局解释器锁-死锁与递归锁-信号量-event事件

    一.全局解释器锁GIL: 官方的解释:掌握概念为主 """ In CPython, the global interpreter lock, or GIL, is a m ...

  4. 同步锁 死锁与递归锁 信号量 线程queue event事件

    二个需要注意的点: 1 线程抢的是GIL锁,GIL锁相当于执行权限,拿到执行权限后才能拿到互斥锁Lock,其他线程也可以抢到GIL,但如果发现Lock任然没有被释放则阻塞,即便是拿到执行权限GIL也要 ...

  5. 线程锁&信号量&gil

    线程锁 线程锁的主要目的是防止多个线程之间出现同时抢同一个数据,这会造成数据的流失.线程锁的作用类似于进程锁,都是为了数据的安全性 下面,我将用代码来体现进程锁的作用: from threading ...

  6. Python进阶----线程基础,开启线程的方式(类和函数),线程VS进程,线程的方法,守护线程,详解互斥锁,递归锁,信号量

    Python进阶----线程基础,开启线程的方式(类和函数),线程VS进程,线程的方法,守护线程,详解互斥锁,递归锁,信号量 一丶线程的理论知识 什么是线程:    1.线程是一堆指令,是操作系统调度 ...

  7. GIL全局解释器锁,线程池与进程池 同步异步,阻塞与非阻塞,异步回调

    GIL全局解释器锁 1.什么是GIL 官方解释:'''In CPython, the global interpreter lock, or GIL, is a mutex that prevents ...

  8. 多进程 multiprocessing 多线程Threading 线程池和进程池concurrent.futures

    multiprocessing.procsess 定义一个函数 def func():pass 在if __name__=="__main__":中实例化 p = process( ...

  9. 并发编程:GIL,线程池,进程池,阻塞,非阻塞,同步,异步

    一  GIL(global interpreter lock) GIL中文叫全局解释器锁,我们执行一个文件会产生一个进程,那么我们知道进程不是真正的执行单位,而是资源单位,所以进程中放有解释器(cpy ...

随机推荐

  1. C# 监控网速

    主要有两个类,其一是NetworkAdapter,该类的作用是获取本机网络适配器列表,并且可以通过该类的属性获取当前网速数据:其二是NetworkMonitor,该类是通过.NET的Performan ...

  2. linux 出错 “INFO: task java: xxx blocked for more than 120 seconds.” 的3种解决方案

    1 问题描述 最近搭建的一个linux最小系统在运行到241秒时在控制台自动打印如下图信息,并且以后每隔120秒打印一次. 仔细阅读打印信息发现关键信息是“hung_task_timeout_secs ...

  3. windbg自行下载的sos.dll存放路径“..\SOS_x86_x86_4.7.3132.00.dll\5B5543296ee000\”里的“5B5543296ee000”是什么?

    问题的引出 我在调试某个崩溃问题时,要跟踪clr的栈,于是,我先执行了指令.loadby sos clrjit,没有报错,然后我又执行!clrstack,结果却有如下输出:0:000:x86> ...

  4. graphql-query-rewriter 无缝处理graphql 变更

    graphql-query-rewriter 是一个graphql schema 变动重写的中间件,可以帮助我们解决在版本变动,查询实体变动 是的问题,从目前已知的技术中我们可选的方案有以下处理变动的 ...

  5. The two of the oldest man need cheers

    At a company dinner, the drinking rule was that two colleagues of similar age clinked glasses of win ...

  6. POJ 1436.Horizontally Visible Segments-线段树(区间更新、端点放大2倍)

    水博客,水一水. Horizontally Visible Segments Time Limit: 5000MS   Memory Limit: 65536K Total Submissions:  ...

  7. ARC093F Dark Horse 【容斥,状压dp】

    题目链接:gfoj 神仙计数题. 可以转化为求\(p_1,p_2,\ldots,p_{2^n}\),使得\(b_i=\min\limits_{j=2^i+1}^{2^{i+1}}p_j\)都不属于\( ...

  8. 洛谷P3147 262144

    题目 此题数据范围小的话可以用区间\(DP\),但是该题目的数据范围并不能用区间DP来求解,因此我们考虑优化\(DP\). 每个数的生成一定是由这两个区间 考虑区间DP的弊端是并不知道每个数生成的区间 ...

  9. mapreduce数据处理——统计排序

    接上篇https://www.cnblogs.com/sengzhao666/p/11850849.html 2.数据处理: ·统计最受欢迎的视频/文章的Top10访问次数 (id) ·按照地市统计最 ...

  10. tengine负载均衡高可用配置

    环境 Tengine-master:192.168.109.100 Tengine-slave:192.168.109.101 tomcat01:192.168.109.102 tomcat02:19 ...