-
死锁现象与递归锁
死锁现象举例 from threading import Thread from threading import Lock import time lock_A = Lock() lock_B = Lock() class MyThread(Thread): def run(self): self.f1() self.f2() def f1(self): lock_A.acquire() print(f'{self.name}抢到了A锁') lock_B.acquire() print(f'{self.name}抢到了B锁') lock_B.release() lock_A.release() def f2(self): lock_B.acquire() print(f'{self.name}抢到了B锁') time.sleep(0.1) lock_A.acquire() print(f'{self.name}抢到了A锁') lock_A.release() lock_B.release() if __name__ == '__main__': for i in range(3): t = MyThread() t.start()
递归锁 # 递归锁有一个计数的功能, 原数字为0,上一次锁,计数+1,释放一次锁,计数-1, # 只要递归锁上面的数字不为零,其他线程就不能抢锁. #递归锁可以解决死锁现象,业务需要多个锁时,先要考虑递归锁 from threading import Thread from threading import RLock import time lock_A = lock_B = RLock() class MyThread(Thread): def run(self): self.f1() self.f2() def f1(self): lock_A.acquire() print(f'{self.name}抢到了A锁') lock_B.acquire() print(f'{self.name}抢到了B锁') lock_B.release() lock_A.release() def f2(self): lock_B.acquire() print(f'{self.name}抢到了B锁') time.sleep(0.1) lock_A.acquire() print(f'{self.name}抢到了A锁') lock_A.release() lock_B.release() if __name__ == '__main__': for i in range(3): t = MyThread() t.start()
-
信号量
2.1 引入Semaphore模块,Semaphore(5)相当于有五把锁同时让人去抢到,之后释放掉几把锁就会有几把锁被抢
2.2 也是一种锁,控制并发数量
from threading import Thread,Semaphore,current_thread import time import random sem = Semaphore(5) def task(): sem.acquire() print(f'{current_thread().name} 厕所ing') time.sleep(random.randint(1,3)) sem.release() if __name__ == '__main__': for i in range(20): t = Thread(target=task,) t.start()
-
GIL全局解释器锁
-
好多自称大神的说,GIL锁就是python的致命缺陷,Python不能多核,并发不行等等 .....
-
理论上来说:单个进程的多线程可以利用多核.但是,开发Cpython解释器的程序员,给进入解释器的线程加了锁.
-
为什么加锁?
- 当时都是单核时代,而且cpu价格非常贵.
- 如果不加全局解释器锁, 开发Cpython解释器的程序员就会在源码内部各种主动加锁,解锁,非常麻烦,各种死锁现象等等.他为了省事儿,直接进入解释器时给线程加一个锁.
- 优点: 保证了Cpython解释器的数据资源的安全.
- 缺点: 单个进程的多线程不能利用多核.
-
Jpython没有GIL锁.pypy也没有GIL锁.
-
现在多核时代, 我将Cpython的GIL锁去掉行么?
- 因为Cpython解释器所有的业务逻辑都是围绕着单个线程实现的,去掉这个GIL锁,几乎不可能.
- 单个进程的多线程可以并发,但是不能利用多核,不能并行.
- 多个进程可以并发,并行.
- io密集型:
- 计算密集型:
-
-
GIL与lock锁的区别
-
相同点: 都是同种锁,互斥锁.
-
不同点:
1. GIL锁全局解释器锁,保护解释器内部的资源数据的安全.
2. GIL锁 上锁,释放无需手动操作.
3. 自己代码中定义的互斥锁保护进程中的资源数据的安全.
4. 自己定义的互斥锁必须自己手动上锁,释放锁.
-
-
验证计算密集型IO密集型的效率
- o密集型:
- 计算密集型:
-
代码验证:
- 计算密集型:多进程并发并行效率高
from threading import Thread from multiprocessing import Process import random import time ##计算密集型:单个进程的多线程并发vs多个进程并发,并行 def task(): count = 0 for i in range(100000000): count += 1 #多进程的并发,并行 if __name__ == '__main__': # start_time = time.time() # l1 = [] # for i in range(4): # p = Process(target=task) # l1.append(p) # p.start() # for i in l1: # i.join() # print(f'执行效率:{time.time()-start_time}')#执行效率:14.23704481124878 #多线程并发 start_time = time.time() l1 = [] for i in range(4): p = Thread(target=task) l1.append(p) p.start() for e in l1: e.join() print(f'执行效率:{time.time()-start_time}')#执行效率:25.48371386528015
- IO密集型:单个进程多线程并发效率高
from threading import Thread from multiprocessing import Process import random import time def task(): count = 0 time.sleep(random.randint(1,3)) count += 1 #多进程的并发,并行 # if __name__ == '__main__': # start_time = time.time() # l1 = [] # for i in range(50): # p = Process(target=task,) # l1.append(p) # p.start() # for e in l1: # e.join() # print(f'执行效率:{time.time()-start_time}')#执行效率:5.1458330154418945 #单个进程多线程并发 if __name__ == '__main__': start_time = time.time() li = [] for i in range(50): t = Thread(target=task,) li.append(t) t.start() for e in li: e.join() print(f'执行效率:{time.time()-start_time}')#执行效率:3.0080912113189697
-
计算密集型:
-
多线程实现socket通信
- 无论是多线程还是多进程,如果按照上面的写法,来一个客户端请求,我就开一个线程,来一个请求开一个线程,
- 应该是这样: 你的计算机允许范围内,开启的线程进程数量越多越好.
server端 import socket from threading import Thread def communicate(conn,addr): while 1: try: from_client_data = conn.recv(1024) print(f'收到来自客户端{addr[0]}的信息:{from_client_data.decode("utf-8")}') to_client_data = input('>>>').strip() conn.send(to_client_data.encode('utf-8')) except Exception: break conn.close() def _accept(): server = socket.socket() server.bind(('127.0.0.1', 8849)) server.listen(5) while 1: conn, addr = server.accept() t = Thread(target=communicate,args=(conn,addr)) t.start() if __name__ == '__main__': _accept()
client端 import socket client = socket.socket() client.connect(('127.0.0.1',8849)) while 1: try: to_server_data = input('>>>').strip() client.send(to_server_data.encode('utf-8')) from_server_data = client.recv(1024) print(f'来自客户端的消息:{from_server_data.decode("utf-8")}') except Exception: break client.close()
-
进程池,线程池
- 线程池: 一个容器,这个容器限制住你开启线程的数量,比如4个,第一次肯定只能并发的处理4个任务,只要有任务完成,线程马上就会接下一个任务.
- 以时间换空间.
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor import os import time import random # print(os.cpu_count()) def task(n): print(f'{os.getpid()} 接客') time.sleep(random.randint(1,3)) if __name__ == '__main__': # 开启进程池 (并行(并行+并发)) # p = ProcessPoolExecutor() # 默认不写,进程池里面的进程数与cpu个数相等 # # # p.submit(task,1) # # p.submit(task,1) # # p.submit(task,1) # # p.submit(task,1) # # p.submit(task,1) # # p.submit(task,1) # # p.submit(task,1) # for i in range(20): # p.submit(task,i) # # 开启线程池 (并发) t = ThreadPoolExecutor() # 默认不写, cpu个数*5 线程数 # t = ThreadPoolExecutor(100) # 100个线程 for i in range(20): t.submit(task,i)
结合:
server from concurrent.futures import ThreadPoolExecutor from threading import Thread import socket def communicate(conn,addr): while 1: try: from_client_data = conn.recv(1024) print(f'收到来自客户端{addr}的信息:{from_client_data.decode("utf-8")}') to_client_data = input('>>>').strip() conn.send(to_client_data.encode('utf-8')) except Exception: break conn.close() def _accept(): server = socket.socket() server.bind(('127.0.0.1', 8840)) server.listen(5) a = ThreadPoolExecutor(2) while 1: conn, addr = server.accept() t = Thread(target=communicate,args=(conn,addr)) a.submit(communicate,conn,addr) if __name__ == '__main__': _accept()
import socket client = socket.socket() client.connect(('127.0.0.1',8840)) while 1: try: to_server_data = input('>>>').strip() client.send(to_server_data.encode('utf-8')) from_server_data = client.recv(1024) print(f'来自客户端的消息:{from_server_data.decode("utf-8")}') except Exception: break client.close()