锁+线程+协程
今日内容概要
- 死锁与递归锁(了解)
- 信号量(了解)
- Event事件(了解)
- 线程q(了解)
- 进程池与线程池(掌握)
- 协程(了解)
- 协程实现TCP服务端的并发效果(了解)
今日内容详细
-
死锁与递归锁(了解)
当你知道锁的使用抢锁必须要释放锁,其实你在操作锁的时候也极其容易产生死锁现象(整个程序卡死 阻塞)
# 死锁 from threading import Thread,Lock import time mutexA = Lock() mutexB = Lock() # 类只要加括号多次 产生的肯定是不同的对象 # 如果你想要实现多次加括号得到的是相同的对象 单例模式 class MyThead(Thread): def run(self): self.func1() self.func2() def func1(self): mutexA.acquire() print('%s 抢到A锁'%self.name) # 获取当前线程名 mutexB.acquire() print('%s 抢到B锁'%self.name) mutexB.release() mutexA.release() # """ # 分析:Thread-1 抢到A锁,然后打印,注意这里并没有释放A锁,而是直接抢到B锁(没有人能跟他抢) # 然后释放B锁,释放A锁,此时其他人去抢A锁,而Thread-1已经进入func2去抢B锁(也没人跟他抢),因为 # 此时大家还在func1内抢A锁 # """ def func2(self): mutexB.acquire() print('%s 抢到B锁' % self.name) time.sleep(2) mutexA.acquire() print('%s 抢到A锁' % self.name) mutexA.release() mutexB.release() """ 分析:此时Thread-1抢到B锁,然后睡了2秒,想去抢A锁,但是此时Thread-2再func1内抢到了A锁,想抢B锁, 然后现在大家都不能释放锁,因此就形成了死锁,卡死在这 """ if __name__ == '__main__': for i in range(10): t = MyThead() t.start() ''' 输出结果: Thread-1 抢到A锁 Thread-1 抢到B锁 Thread-1 抢到B锁 Thread-2 抢到A锁 然后卡死 '''# 递归锁 ''' 递归锁的特点 可以被连续的acquire和release 但是只能被第一个抢到这把锁的执行上述操作 它的内部有一个计数器 每acquire一次计数加一 每release一次计数减一 只要计数不为0 那么其他人都无法抢到该锁 ''' # 递归锁 from threading import Thread,Lock,RLock import time mutexA = mutexB = RLock() class MyThead(Thread): def run(self): self.func1() self.func2() def func1(self): mutexA.acquire() print('%s 抢到A锁'%self.name) # 获取当前线程名 mutexB.acquire() print('%s 抢到B锁'%self.name) mutexB.release() mutexA.release() # """ # 分析:Thread-1 抢到A锁,引用计数+1,然后打印,注意这里并没有释放A锁,而是直接抢到B锁(没有人能跟他抢),引用计数再+1 # 然后释放B锁(引用计数-1),释放A锁(引用计数-1),此时其他人去抢A锁,而Thread-1已经进入func2去抢B锁(也没人跟他抢),因为 # 此时大家还在func1内抢A锁 # """ def func2(self): mutexB.acquire() print('%s 抢到B锁' % self.name) time.sleep(2) mutexA.acquire() print('%s 抢到A锁' % self.name) mutexA.release() mutexB.release() """ 分析:此时Thread-1抢到B锁(引用计数+1),然后睡了2秒,去抢A锁(引用计数+1),此时其他人还在func1内, 然后释放A锁(引用计数-1),释放B锁(引用计数-1),引用计数为0后,下一位继续 """ if __name__ == '__main__': for i in range(10): t = MyThead() t.start() -
信号量(了解)
信号量在不同的阶段可能对应不同的技术点
在并发编程中信号量指的是锁!!!
''' 如果我们将互斥锁比喻成一个厕所的话,大家要一个一个去抢厕所 那么信号量就相当于多个厕所,比如有5个厕所,20个人抢5个,然后再15个抢5个 ''' from threading import Thread,Semaphore import random import time sm = Semaphore(5) # 括号内写数字 写几就表示开设几个坑位 def task(name): sm.acquire() print('%s正在蹲坑'%name) time.sleep(3) time.sleep(random.randint(1,5)) sm.release() if __name__ == '__main__': for i in range(20): t = Thread(target=task,args=('伞兵%s号'%i,)) -
Event事件(了解)
一些进程/线程需要等待另外一些进程/线程运行完毕之后才能运行,类似于发射信号一样
from threading import Thread,Event import time event = Event() # 造了一个红路灯 def light(): print('红灯亮着') time.sleep(3) print('绿灯亮了') # 告诉等红灯的人可以走了 event.set() def car(name): print('%s 车正在等红灯'%name) event.wait() # 等待别人给你发信号 print('%s 车加油门飙车走了'%name) if __name__ == '__main__': t = Thread(target=light) t.start() for i in range(20): t = Thread(target=car,args=('%s'%i,)) t.start() -
线程q(了解)
''' 同一个进程下多个线程数据是共享的 为什么在同一个进程下还会去使用队列呢 因为队列是 管道 + 锁 所以用队列还是为了保证数据的安全 ''' import queue # 我们现在使用的队列都是只能在本地测试使用 # 1、队列q 先进先出 # q = queue.Queue(3) # q.put(1) # q.get() # q.get_nowait() # q.get(timeout=3) # q.full() # q.empty() # 后进先出q # q = queue.LifoQueue(3) # Lifo:last in first out # q.put(1) # q.put(2) # q.put(3) # print(q.get()) # 3 # 优先级q 你可以给放入队列中的数据设置进出的优先级 q = queue.PriorityQueue(4) q.put((10,'111')) # 以元组形式存放,10代表优先级,'111'代表存放数据 q.put((100,'222')) q.put((0,'333')) q.put((-5,'444')) print(q.get()) # (-5, '444') # put括号内存放一个元组 第一个放数字表示优先级 # 需要注意的是 数字越小优先级越高 -
进程池与线程池(掌握)
先回顾之前TCP服务端实现并发的效果是怎么玩的
每来一个人就开设一个进程或一个线程
''' 无论是开设进程也好还是开设线程也好 是不是都需要消耗资源 只不过开设线程得消耗比开设进程的稍微小一点而已 我们是不可能做到无限制的开设进程和线程的 因为计算机硬件的资源跟不上 硬件的开发速度远远赶不上软件 我们的宗旨应该是在保证计算机硬件能够正常工作的情况下最大的限度利用他 ''' ''' 什么是池? 池是用来保证计算机硬件安全的情况下最大限度的利用计算机 它降低了程序的运行效率但是保证了计算机硬件的安全 从而让你写的程序能够正常运行 '''基本使用
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor import time import os # pool = ThreadPoolExecutor(5) # 池子里面固定只有五个线程 # 括号内可以传数字 不传的话默认会开设当前计算机cpu个数五倍的线程 pool = ProcessPoolExecutor(5) # 括号内可以传数字 不传的话默认会开设当前计算机cpu个数的进程 ''' 池子造出来以后 里面会固定存在5个线程 这5个线程不会出现重复创建和销毁的过程 池子造出来以后 里面会固定存在5个进程 这5个进程不会出现重复创建和销毁的过程 池子的使用非常简单 你只需要将需要做的任务往池子中提交即可 自动会有人来服务你 ''' def task(n): print(n,os.getpid()) # 查看当前进程的进程号 time.sleep(2) return n**n def call_back(n): print('call_back==>:',n.result()) ''' 任务的提交方式: 同步:提交任务之后原地等待任务的返回结果 期间不做任何事 异步:提交任务之后不等待任务的返回结果 直接继续往下执行 返回结果如何获取? 异步提交任务的返回结果 应该通过回调机制来获取 回调机制 就相当于给每个异步任务绑定了一个定时炸弹 一旦任务有结果立刻触发爆炸 ''' if __name__ == '__main__': # pool.submit(task,1) # 超池子中提交任务 异步提交 # print('主') t_list = [] for i in range(20): # 朝池子中提交20个任务 # res = pool.submit(task,i) # <Future at 0x18966277310 state=running> res = pool.submit(task,i).add_done_callback(call_back) # print(res.result()) # result 方法 变成同步提交 # t_list.append(res) # pool.shutdown() # 关闭线程池 等待线程池中所有的任务运行完毕 # for t in t_list: # print('==>:',t.result()) # 肯定是有序的 ''' 程序由并发变成了串行 任务为什么打印的是None res.result() 拿到的就是异步提交的任务的返回结果,因为def task()函数并未返回结果,所以是None '''总结
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor pool = ProcessPoolExecutor(5) res = pool.submit(task,i).add_done_callback(call_back) -
协程(了解)
'''进程:资源单位线程:执行单位协程:这个概念完全是程序员自己意淫出来的 根本不存在 单线程下实现并发 我们程序员自己在代码层面上检测我们所有的IO操作 一旦遇到IO了 我们在代码级别完成切换 这样给CPU的感觉是你这个程序一直在运行 没有IO 从而提升程序的运行效率 多道技术 切换+保存状态 CPU两种切换 1.程序遇到IO 2.程序长时间占用 TCP服务端 accept recv 每当两者有IO时,协程就会切换,谁阻塞就切换谁代码如何做到 切换+保存状态切换 切换不一定是提升效率 也有可能是降低效率 IO切 提升 没有IO切 降低保存状态 保存上一次我执行的状态 下一次来接着上一次的操作继续往后执行 yield'''验证切换是否就是提升效率
# import time## # 串行执行计算密集型的任务 # 5.6023218631744385# def func1():# for i in range(100000000):# i + 1## def func2():# for i in range(100000000):# i + 1## start_time = time.time()# func1()# func2()# print(time.time() - start_time)# 切换+yield # 6.719145059585571import timedef func1(): while True: 100000000+1 yielddef func2(): g = func1() # 先初始化生成器 for i in range(100000000): i + 1 next(g)start_time = time.time()func2()print(time.time() - start_time) -
gevent模块(了解)
# import time## # 串行执行计算密集型的任务 # 5.6023218631744385# def func1():# for i in range(100000000):# i + 1## def func2():# for i in range(100000000):# i + 1## start_time = time.time()# func1()# func2()# print(time.time() - start_time)# 切换+yield # 6.719145059585571# import time### def func1():# while True:# 100000000+1# yield## def func2():# g = func1() # 先初始化生成器# for i in range(100000000):# i + 1# next(g)## start_time = time.time()# func2()# print(time.time() - start_time)import timefrom gevent import spawnfrom gevent import monkey;monkey.patch_all()'''gevent模块本身无法检测常见的一些IO操作在使用的时候需要你额外的导入一句话from gevent import monkeymonkey.patch_all()又由于上面的两句话在使用gevent模块的时候是肯定要导入的所以还支持简写 from gevent import monkey;monkey.patch_all()'''def heng(): print('哼') time.sleep(2) print('哼')def ha(): print('哈') time.sleep(3) print('哈')start = time.time()g1 = spawn(heng)g2 = spawn(ha)g1.join() # 等待被检测的任务执行完毕 再往后继续执行g2.join()# heng()# ha()# print(time.time()-start) # 5.024355888366699print(time.time()-start) # 3.0243263244628906 来回切换 时常其实就是时间最长的那个-
协程实现TCP服务端的并发
# 服务端from gevent import monkey;monkey.patch_all()import socketfrom gevent import spawndef communication(conn): while True: try: data = conn.recv(1024) if len(data) == 0:break conn.send(data.upper()) except ConnectionResetError as e: print(e) break conn.close()def server(ip,port): server = socket.socket() server.bind((ip,port)) server.listen(5) while True: conn,addr = server.accept() spawn(communication,conn)if __name__ == '__main__': g1 = spawn(server,'127.0.0.1',8080) g1.join()# 客户端from threading import Thread,current_threadimport socketdef x_client(): client = socket.socket() client.connect(('127.0.0.1',8080)) n = 0 while True: msg = '%s say hello %s'%(current_thread().name,n) n += 1 client.send(msg.encode('utf-8')) data = client.recv(1024) print(data.decode('utf-8'))if __name__ == '__main__': for i in range(500): t = Thread(target=x_client,) t.start()总结
'''理想状态: 我们可以通过 多进程下面开设多线程 多线程下面再开设协程序 从而使我们的程序执行效率提升'''
-

浙公网安备 33010602011771号