TCP服务端实现并发
1.将不一样的功能尽可能拆分红不一样的函数
拆分出来的功能能够被多个地方使用
2.将链接循环和通讯循环拆分红不一样的函数
3.将通讯循环作成多线程html
import socket from threading import Thread import time """ 服务端 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')) time.sleep(10) #开启一个客户端的时候10s打印一个结果,多个客户端就会有并发效果,开10个感受上10s打印10个 conn.send(data.upper()) except ConnectionResetError as e: print(e) break conn.close() 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'))
1. 只有python有GIL吗?python
GIL全局解释器锁目前是全部解释型语言的通病不可调和,必须加锁,编译型语言在编译的时候已经管理好了多线程运行的问题
有人给处理的数据加锁,虽然能够,可是那样就会有不少锁,出现大量的串行,给解释器加锁虽然也是串行,可是至少还能够实现并发
web
2. GIL全局解释器锁的存在是python的问题么?
GIL并非Python的特性,Python彻底能够不依赖于GIL,只是Cpython的内存管理不是线程安全,就引入这一个概念,JPython就不存在安全
内存管理(垃圾回收机制)
引用计数: 值与变量的绑定关系的个数
标记清除: 当内存快要满的时候 会自动中止程序的运行 检测全部的变量与值的绑定关系
给没有绑定关系的值打上标记,最后一次性清除
分代回收: (垃圾回收机制也是须要消耗资源的,而正常一个程序的运行内部会使用到不少变量与值
而且有一部分相似于常量,减小垃圾回收消耗的时间,应该对变量与值的绑定关系作一个分类)
新生代(5S)》》》青春代(10s)》》》老年代(20s)
垃圾回收机制扫描必定次数发现关系还在,会将该对关系移至下一代
随着代数的递增 扫描频率是下降的多线程
3. 做用原理:并发
将并发运行变成串行,牺牲效率来提升数据的安全(全部互斥锁的本质)
控制同一时间内共享数据只能被一个线程所修改(不能并行可是可以实现并发)app
4.缘由详解
一个进程中必带一个解释器和一个垃圾回收线程
一个进程下的多个线程都须要运行,就必须去调解释器,垃圾回收线程也要用解释器,
若是不加限制,若是回收机制和其余线程同时使用解释器,会同时执行,回收机制就可能误删掉那些刚建立还没来得及绑定的变量资源
所以必须给解释器加锁,只能容许一个线程使用,我干活的时候你滚一边去,不要干扰我,这样才不会冲突
进程能够利用多核,可是开销大,python的多线程开销小,但却没法利用多核优点,难道说说python多线程没用了?dom
同一个进程下的多个线程虽不能实现并行,可是可以实现并发
多个进程下的线程可以实现并行,发挥多核优点socket
对计算来讲,cpu越多越好,可是对于I/O来讲,再多的cpu也没用ide
固然对运行一个程序来讲,随着cpu的增多执行效率确定会有所提升(无论提升幅度多大,总会有所提升)
这是由于一个程序基本上不会是纯计算或者纯I/O
因此咱们只能相对的去看一个程序究竟是计算密集型仍是I/O密集型,从而进一步分析python的多线程到底有无用武之地
#分析: 咱们有四个任务须要处理,处理方式确定是要玩出并发的效果,解决方案能够是: 方案一:开启四个进程 方案二:一个进程下,开启四个线程 #单核状况下:
若是四个任务是计算密集型,没有多核来并行计算,方案一徒增了建立进程的开销,开线程牛逼 若是四个任务是I/O密集型,建立进程的开销大,且进程的切换速度远不如线程,开线程牛逼 #多核状况下:
若是四个任务是计算密集型,多核意味着并行计算,在python中一个进程中同一时刻只有一个线程执行用不上多核,开进程牛逼 若是四个任务是I/O密集型,再多的核去开再多进程仍是要一个个等IO的时间,也解决不了I/O问题,多线程节约了资源还交互进行提升效率,开线程牛逼
目前计算机都是多核,因此:
计算密集型:多线程还不如串行(没有大量切换),多核多进程牛逼
IO密集型: 多线程明显提升效率,多核没屌用
多线程和多进程都有本身的优势,要根据项目需求合理选择
目前大多数软件都是IO密集型:socket 爬虫 web
也有计算密集型的:金融分析
# IO密集型 from multiprocessing import Process from threading import Thread import threading import os,time def work(): time.sleep(2) #睡觉也是典型的IO操做 if __name__ == '__main__': l=[] print(os.cpu_count()) #获取本机cpu数量 start=time.time() for i in range(300): # p=Process(target=work) #用进程跑 耗时12.69s p=Thread(target=work) #用线程跑 耗时2.03s l.append(p) p.start() for p in l: p.join() stop=time.time() print('run time is %s' %(stop-start))
# 计算密集型 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()) # 本机为4核 start=time.time() for i in range(8): p=Process(target=work) #耗时24.21s # p=Thread(target=work) #耗时43.61s l.append(p) p.start() for p in l: p.join() stop=time.time() print('run time is %s' %(stop-start))
对于不一样的数据,要想保证安全,须要加不一样的锁处理
GIL并不能保证数据的安全,它是对Cpython解释器加锁,针对的是线程
保证的是同一个进程下多个线程之间的安全
#GIL内置存在,只容许一个线程经过,但数据依然不安全
from threading import Thread import time n = 100 def task(): global n tmp = n time.sleep(0.1) # 在睡觉的状况下,输出99,1拿到全局锁遇到IO去睡觉时,GIL必须交出来,2抢到,1还没运行完,0.1s足够你们能抢一遍GIL,因此都拿到100去睡觉了 # 不睡觉的状况下,输出0,1抢到GIL没有IO会一直拿着直到输出后释放,释放后2才能抢到GIL才能继续搞 n = tmp -1 t_list = [] for i in range(100): t = Thread(target=task) t.start() t_list.append(t) for t in t_list: t.join() print(n)
#加自定义锁保证数据安全
from threading import Thread,Lock import time n = 100 mutex = Lock() def task(): global n tmp = n #99 放在这里,tmp受到GIL控制,GIL尚未出计算结果就释放,0.1s的睡眠时间GIL够你们搞一遍了,也就是都获得了tmp=100 mutex.acquire() # tmp = n #0 放在这里,tmp受到自定义锁控制,自定义锁必须在运行结束才能释放,你们拿到的n都是上次结果-1 time.sleep(0.1) n = tmp -1 mutex.release() t_list = [] for i in range(100): t = Thread(target=task) t.start() t_list.append(t) for t in t_list: t.join() print(n)
进程也有死锁与递归锁,在进程那里忘记说了,放到这里一切说了额
1.只要类加括号实例化对象
不管传入的参数是否同样生成的对象确定不同,不信你打印id
单例模式除外
mutexA = Lock() mutexB = Lock()
mutexA 和 mutexB 可不是同样东西
2.链式赋值出来的对象那但是一毛同样 mutexA = mutexB = RLock() # A B如今是同一把锁
lock锁 一次acquire必须对应一次release,不能连续acquire
所谓死锁: 是指两个或两个以上的进程或线程在执行过程当中,因争夺资源而形成的一种互相等待的现象,(你拿了我想要的锁,我拿了你想要的锁)
若无外力做用,它们都将没法推动下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程
以下就是死锁:
from threading import Thread,Lock,current_thread,RLock import time """ 自定义锁一次acquire必须对应一次release,不能连续acquire 本身千万不要轻易的处理锁的问题 """ mutexA = Lock() mutexB = Lock() 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(3): t = MyThread() t.start() ''' Thread-1抢到了A锁 Thread-1抢到了B锁 Thread-1释放了B锁 Thread-1释放了A锁 Thread-1抢到了B锁 Thread-2抢到了A锁 #到这里以后就出现死锁了,你想要的个人锁我想要你的锁 '''
解决死锁方法,递归锁,在Python中为了支持在同一线程中屡次请求同一资源,python提供了可重入锁RLock。
这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源能够被屡次require。直到一个线程全部的acquire都被release,其余的线程才能得到资源。上面的例子若是使用RLock代替Lock,则不会发生死锁:
Rlock能够被第一个抢到锁的人连续的acquire和release
每acquire一次锁身上的计数加1
每release一次锁身上的计数减1
只要锁的计数不为0 其余人都不能抢
from threading import Thread,Lock,current_thread,RLock import time """ Rlock能够被第一个抢到锁的人连续的acquire和release 每acquire一次锁身上的计数加1 每release一次锁身上的计数减1 只要锁的计数不为0 其余人都不能抢 """ mutexA = mutexB = RLock() # A B如今是同一把锁,抢锁以后会有一个计数 抢一次计数加一 针对的是第一个抢到个人人 print(id(mutexB)) #35379080 print(id(mutexA)) #35379080 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(3): t = MyThread() t.start()
信号量可能在不一样的领域中 对应不一样的知识点
互斥锁:一个厕所(一个坑位)
信号量:公共厕所(多个坑位)
from threading import Semaphore,Thread import time import random sm = Semaphore(3) # 造了一个含有五个的坑位的公共厕所 def task(name): sm.acquire() print('%s占了一个坑位'%name) time.sleep(random.randint(1,30)) print('%s放出一个坑位' % name) sm.release() for i in range(5): t = Thread(target=task,args=(i,)) t.start() ''' 0占了一个坑位 1占了一个坑位 2占了一个坑位 2放出一个坑位 3占了一个坑位 0放出一个坑位 4占了一个坑位 1放出一个坑位 3放出一个坑位 4放出一个坑位 '''
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(3): t = Thread(target=car,args=('伞兵%s'%i,)) t.start() ''' 红灯正亮着 伞兵0正在等红灯 伞兵1正在等红灯 伞兵2正在等红灯 绿灯亮了 伞兵1加油门飙车了 伞兵2加油门飙车了 伞兵0加油门飙车了 '''
同一个进程下的多个线程原本就是数据共享 为何还要用队列
由于队列是管道+锁 使用队列你就不须要本身手动操做锁的问题
由于锁操做的很差极容易产生死锁现象
1.正常队列 先进先出 q = queue.Queue() q.put('hahha') print(q.get()) 2.先进后出 q = queue.LifoQueue() q.put(1) q.put(2) q.put(3) print(q.get()) #3 3.自定义优先级 越小越优先 q = queue.PriorityQueue() q.put((10,'haha')) q.put((100,'hehehe')) q.put((0,'xxxx')) q.put((-10,'yyyy')) print(q.get()) #(-10, 'yyyy')