使服务器支持并发、GIL全局解释器锁、死锁和Rlock、信号量、event事件、
服务器的并发实现:
服务端:
import socket from threading import Thread """ 服务端 1.要有固定的IP和PORT 2.24小时不间断提供服务 3.能够支持并发 """ server = socket.socket() server.bind(('127.0.0.1',8080)) server.listen(5) def talk(conn): while True: try: data = conn.recv(1024) if len(data) == 0:break print(data.decode('utf-8')) conn.send(data.upper()) except ConnectionResetError as e: print(e) break conn.close() """ 当有客户端过来连接的时候,先执行while循环中的代码, 开启一个线程去服务这个客户端,当有客户端再过来连的时候, 可以再开启一个线程去服务一个客户端 """ while True: conn, addr = server.accept() # 监听 等待客户端的连接 阻塞态 print(addr) t = Thread(target=talk, args=(conn,)) t.start()
客户端:
import socket client = socket.socket() client.connect(('127.0.0.1',8080)) while True: client.send(b'hello') data = client.recv(1024) print(data.decode('utf-8'))
GIL全局解释器锁:
Python代码的执行由Python虚拟机(也叫解释器主循环)来控制。Python在设计之初就考虑到要在主循环中,同时只有一个线程在执行。
虽然 Python 解释器中可以“运行”多个线程,但在任意时刻只有一个线程在解释器中运行。
对Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。
GIL本质也是一把互斥锁:将并发变成串行牺牲效率保证数据的安全
用来阻止同一个进程下的多个线程的同时执行(同一个进程内多个线程无法实现并行但是可以实现并发)
GIL的存在是因为CPython解释器的内存管理不是线程安全的
如果同时运行,那么线程中的值可能还没有被使用就被垃圾回收机制进行回收了,所以CPython解释器的内存管理不是线程安全的,要有GIL。
垃圾回收机制
1.引用计数
2.标记清除
3.分代回收
研究python的多线程是否有用需要分情况讨论:
四个任务 计算密集型的
单核情况下
开线程更省资源
多核情况下
开进程
开线程
四个任务 IO密集型的
单核情况下
开线程更节省资源
多核情况下
开线程更节省资源
代码验证:
计算密集型:
计算密集型 from multiprocessing import Process from threading import Thread import os,time def work(): res=0 for i in range(100000000): res*=i if __name__ == '__main__': l=[] print(os.cpu_count()) # 本机为6核 start=time.time() for i in range(6): # p=Process(target=work) #耗时 4.732933044433594 p=Thread(target=work) #耗时 22.83087730407715 l.append(p) p.start() for p in l: p.join() stop=time.time() print('run time is %s' %(stop-start))
IO密集型:
from multiprocessing import Process from threading import Thread import threading import os,time def work(): time.sleep(2) if __name__ == '__main__': l=[] print(os.cpu_count()) #本机为6核 start=time.time() for i in range(40): # p=Process(target=work) #耗时9.001083612442017s多,大部分时间耗费在创建进程上 p=Thread(target=work) #耗时2.051966667175293s多 l.append(p) p.start() for p in l: p.join() stop=time.time() print('run time is %s' %(stop-start))
死锁和Rlock:
死锁状态:也就是不同线程都去等待对方去释放锁的情况(互相僵持)
建议:轻易不要去尝试处理锁的问题。
from threading import Thread,Lock,current_thread,RLock import time """ Rlock可以被第一个抢到锁的人连续的acquire和release 每acquire一次锁身上的计数加1 每release一次锁身上的计数减1 只要锁的计数不为0 其他人都不能抢 """ # mutexA = Lock() # mutexB = Lock() mutexA = mutexB = RLock() # A B现在是同一把锁 class MyThread(Thread): def run(self): # 创建线程自动触发run方法 run方法内调用func1 func2相当于也是自动触发 self.func1() self.func2() def func1(self): mutexA.acquire() print('%s抢到了A锁'%self.name) # self.name等价于current_thread().name mutexB.acquire() print('%s抢到了B锁'%self.name) mutexB.release() print('%s释放了B锁'%self.name) mutexA.release() print('%s释放了A锁'%self.name) def func2(self): mutexB.acquire() print('%s抢到了B锁'%self.name) time.sleep(1) mutexA.acquire() print('%s抢到了A锁' % self.name) mutexA.release() print('%s释放了A锁' % self.name) mutexB.release() print('%s释放了B锁' % self.name) for i in range(10): t = MyThread() t.start()
信号量:
过程:
先有五个线程同时获得锁,然后运行完因为不同线程睡眠时间不同所以释放时间也会不同,
之后先释放出来的锁便可以让其他线程再去抢夺。
# 信号量可能在不同的领域中 对应不同的知识点 """ 互斥锁:一个厕所(一个坑位) 信号量:公共厕所(多个坑位) """ from threading import Semaphore,Thread import time import random sm = Semaphore(5) # 造了一个含有五个的坑位的公共厕所 def task(name): sm.acquire() print('%s占了一个坑位'%name) time.sleep(random.randint(1,3)) sm.release() for i in range(40): t = Thread(target=task,args=(i,)) t.start()
envent事件:
from threading import Event, Thread import time # 先生成一个event对象 e = Event() def light(): print('红灯正亮着') time.sleep(3) e.set() # 发信号 print('绿灯亮了') def car(name): print('%s正在等红灯'%name) e.wait() # 等待信号 print('%s加油门飙车了'%name) t = Thread(target=light) t.start() for i in range(10): t = Thread(target=car,args=('伞兵%s'%i,)) t.start() """ 先启一个线程,打印路灯亮了,进入睡眠的同时,起了十个线程,之后打印正在等绿灯, 之后等待e.wait()收到信号,当运行e.set()发信号时,便可以继续运行了。 """
线程queue:
import queue """ 同一个进程下的多个线程本来就是数据共享 为什么还要用队列 因为队列是管道+锁 使用队列你就不需要自己手动操作锁的问题 因为锁操作的不好极容易产生死锁现象 """ # q = queue.Queue() # q.put('hahha') # print(q.get()) # q = queue.LifoQueue() # 后进先出 # q.put(1) # q.put(2) # q.put(3) # print(q.get()) # q = queue.PriorityQueue() # # 数字越小 优先级越高 # q.put((10,'haha')) # q.put((100,'hehehe')) # q.put((0,'xxxx')) # q.put((-10,'yyyy')) # print(q.get())