并发编程其它
内容概要
一、死锁与递归锁(了解)
二、信号量(了解)
三、Event事件(了解)
四、其它线程q(了解)
五、进程池与线程池
六、协程
七、协程实现TCP并发(了解)
1、死锁与递归锁
虽然互斥锁的语法看起来很简单,就是一acquire对应一release,但是在多并发情况下,使用多把互斥锁容易造成死锁现象
死锁现象例子
from threading import Thread,Lock import time # metuxA = Lock() # metuxB = Lock() # 锁A和锁B是两把不同的锁 class MyThread(Thread): def run(self): self.func1() self.func2() def func1(self): metuxA.acquire() print('{}抢到了锁A'.format(self.name)) metuxB.acquire() print('{}抢到了锁B'.format(self.name)) metuxA.release() metuxB.release() def func2(self): metuxB.acquire() print('{}抢到了锁B'.format(self.name)) time.sleep(2) metuxA.acquire() print('{}抢到了锁A'.format(self.name)) metuxA.release() metuxB.release() if __name__ == '__main__': for i in range(10): t = MyThread() t.start()
执行这个程序,会发生线程1抢了锁A,想抢锁B;但是线程2已经抢了锁B,它又想抢锁A,两个线程彼此卡住。
要想解决死锁现象,可以将所有不同的锁设置为同一把递归锁
Rlock是递归锁
from threading import Thread,RLock import time metuxA = metuxB = RLock() # 递归锁的特点: ''' 递归锁可以被连续acquire和release,当每次acquire时,锁内部的计数器自动加一; 当每次release时,锁内部的计数器自动减一。当计数为0时,锁才能真正被释放,其它线程才能枪锁 ''' class MyThread(Thread): def run(self): self.func1() self.func2() def func1(self): metuxA.acquire() print('{}抢到了锁A'.format(self.name)) metuxB.acquire() print('{}抢到了锁B'.format(self.name)) metuxA.release() metuxB.release() def func2(self): metuxB.acquire() print('{}抢到了锁B'.format(self.name)) time.sleep(2) metuxA.acquire() print('{}抢到了锁A'.format(self.name)) metuxA.release() metuxB.release() if __name__ == '__main__': for i in range(10): t = MyThread() t.start()
2、信号量
如果说Lock是多个人抢一把锁,那么信号量就是多个人可以抢多把锁
Semaphore是信号量,Semaphore中可以设置参数,参数的值表示的是同一时间内能够被抢去使用的锁的数量
from threading import Thread,Semaphore import time import random sm = Semaphore(5) def work(i): sm.acquire() print('伞兵{}号准备就绪'.format(i)) time.sleep(random.randrange(2,5)) print('伞兵{}号落地成盒'.format(i)) sm.release() if __name__ == '__main__': for i in range(1,21): t = Thread(target=work,args=(i,)) t.start()
3、Event事件
一般来说,主线程会等待所有非守护线程结束之后才结束
Event事件用于实现子线程之间彼此等待,或者子线程等待主线程
from threading import Thread, Event import time event = Event() def traffic_light(): print('红灯亮了') print(event.is_set()) # 打印_flag当前的值 time.sleep(8) print('绿灯亮了') event.set() # 设置_flag为True print(event.is_set()) # 打印_flag当前的值 def car(i): print('{}车在等待'.format(i)) event.wait() # 执行到这里,判断_flag的值,False时阻塞;True时同行 print('{}车开了'.format(i)) if __name__ == '__main__': t2 = Thread(target=traffic_light) t2.start() for i in range(1,10): t = Thread(target=car,args=(i,)) t.start() event.clear() # 设置_flag为False
4、其它线程q
# 堆栈队列 import queue # 堆栈队列:后进先出 q = queue.LifoQueue(4) q.put(11) q.put(22) q.put(33) q.put(44) print(q.get()) print(q.get()) print(q.get()) print(q.get()) # 优先级队列 q = queue.PriorityQueue(4) q.put((22,'wwww')) q.put((-2,'eeee')) q.put((222,'qqqq')) print(q.get()) print(q.get()) print(q.get()) ''' 优先级队列put方法要传入一个元组,该元组第一个值表示数据提取的优先级,可以为负数,且数字越小,优先级越高'''
5、线程池与进程池
-线程池和进程池:开设固定数量的线程或进程来执行任务的方式就称之为池。
池的提出是为基于计算机硬件实际出发的。
因为无论是开设进程或是线程,都会损耗系统资源(线程开销小,但是仍会有消耗),如果提交一个任务就开设一个进程或者进程,那么当提交的任务的数量很多的时候,所要开设的进程
和线程数量会非常多,这就会导致系统资源被占用过多。
于是提出了池,用固定数量的线程或是进程来执行那些任务。
显然,池的使用会拖慢程序执行的效率,但是保证了计算机硬件的安全
线程池,进程池的开启需要导入concurrent.futures模块
开启进程池
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor import os from threading import current_thread import time def task(i): print('{}当前进程号: {}'.format(i,os.getpid())) print('{}当前线程号: {}'.format(i,current_thread())) print('==========') time.sleep(2) if __name__ == '__main__': pool = ProcessPoolExecutor(5) # 开启有五个子进程的进程池 for i in range(20): # 循环提交任务 pool.submit(task,i) # (要提交的任务,任务需要的参数)
开启进程池
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor import os from threading import current_thread import time def task(i): print('{}当前进程号: {}'.format(i,os.getpid())) print('{}当前线程号: {}'.format(i,current_thread())) print('==========') time.sleep(2) if __name__ == '__main__': pool = ThreadPoolExecutor(5) # 开启有五个子进程的进程池 for i in range(20): # 循环提交任务 pool.submit(task,i) # (要提交的任务,任务需要的参数)
-线程池与线程池拿到任务执行的结果(这里可以理解为函数的返回值)
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor import os from threading import current_thread import time def task(i): print('{}当前进程号: {}'.format(i,os.getpid())) print('{}当前线程号: {}'.format(i,current_thread())) print('==========') time.sleep(2) if __name__ == '__main__': pool = ThreadPoolExecutor(5) # 开启有五个子进程的进程池 for i in range(20): # 循环提交任务 res = pool.submit(task,i) # res拿到的不是task函数的返回值,而是一个执行结果对象<Future at 0x3c1d688 state=pending> print(res)
-要想得到执行结果,在对象下使用成员方法result获取结果
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor import os from threading import current_thread import time def task(i): print('{}当前进程号: {}'.format(i,os.getpid())) print('{}当前线程号: {}'.format(i,current_thread())) print('==========') time.sleep(2) if __name__ == '__main__': pool = ThreadPoolExecutor(5) # 开启有五个子进程的进程池 for i in range(20): # 循环提交任务 res = pool.submit(task,i) # res拿到的不是task函数的返回值,而是一个执行结果对象 print(res.result())
-如果使用上述代码,会发现程序的执行变成了串行。这是因为当执行到res = pool.submit(task,i)时,相当于一个线程开始执行task任务,执行下一行代码print(res.result())时,要打印task任务的
执行结果,那么必须保证task任务执行完毕才行,此时程序会在res.result处有阻塞,直到线程完成task任务,才进入下一次for循环,开启新的任务。因此程序变成了串行。
-于是我们要保证先使所有任务并发之后,再获取执行结果,可以利用之前的列表缓存方法
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor import os from threading import current_thread import time def task(i): print('{}当前进程号: {}'.format(i,os.getpid())) print('{}当前线程号: {}'.format(i,current_thread())) print('==========') time.sleep(2) if __name__ == '__main__': pool = ThreadPoolExecutor(5) # 开启有五个子进程的进程池 l = [] for i in range(20): # 循环提交任务 res = pool.submit(task,i) # res拿到的不是task函数的返回值,而是一个执行结果对象 l.append(res) for obj in l: print(obj.result())
-最好的方式是使用回调机制,保证异步的任务一旦完成,就将结果对象(不是函数的返回值,要想使用返回值,调用result)自动传入到回调函数中去,并且执行回调函数
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor import os from threading import current_thread import time
# 我是回调函数 def call_back(obj): print(obj.result() * 2) def task(i): # print('{}当前进程号: {}'.format(i,os.getpid())) # print('{}当前线程号: {}'.format(i,current_thread())) return i if __name__ == '__main__': pool = ThreadPoolExecutor(5) for i in range(20): res = pool.submit(task,i).add_done_callback(call_back) # 为任务绑定回调函数
6、协程
-协程并不真正存在,协程的本质是一种特殊的线程,是程序员们提出的;
-协程可以从代码级别实现单线程遇到阻塞时,进程执行代码的切换+保存状态;
-协程可以在单线程下实现并发效果
ps:多线程下,如果一个线程遇到io阻塞,操作系统虽然会自动切换cpu使用权限到其它线程,但是这种切换比起代码间切换有两种问题
-cpu切换是由操作系统完成的,切换花费的时间要比单线程下不同位置的代码切换要慢的多;
-操作系统要统筹全局,可能这次cpu切换不是切换到当前进程下的其它线程,而是切换到了其它进程下的线程。
要使用协程,需要导入gevent模块;在此之前,先导入monkey模块,使用monkey(猴子补丁)下的patch_all方法,使gevent模块能够在代码层面识别io阻塞
开启协程
from gevent import monkey monkey.patch_all() import gevent import time def func1(): while 1: print('函数1开始执行') time.sleep(2) print('函数1执行结束') def func2(): while 1: print('函数2开始执行') time.sleep(2) print('函数2执行结束') g1 = gevent.spawn(func1) g2 = gevent.spawn(func2) g1.join() g2.join()
注意的是协程默认设置为 守护线程
7、利用协程实现TCP服务端并发
from gevent import monkey monkey.patch_all() import socket import gevent def talk(conn): while 1: try: data = conn.recv(1024) if not len(data): conn.close() break print(data.decode('utf-8')) conn.send(b'hello world') except ConnectionResetError as e: print(e) conn.close() break def service(): while 1: conn, addr = server.accept() gevent.spawn(talk,conn) # return 1 当函数有返回值时,会导致函数执行完毕 server = socket.socket() server.bind(('127.0.0.1',8080)) server.listen() g1 = gevent.spawn(service) g1.join()
-执行的函数如果带有返回值,func2"起死回生"
from gevent import monkey monkey.patch_all() import gevent import time def func1(): while 1: print('函数1开始执行') time.sleep(2) print('函数1执行结束') def func2(): while 1: print('函数2开始执行') time.sleep(2) print('函数2执行结束') return g1 = gevent.spawn(func1) g2 = gevent.spawn(func2) g1.join() g2.join()
本文来自博客园,作者:口乞厂几,转载请注明原文链接:https://www.cnblogs.com/laijianwei/p/14446303.html

浙公网安备 33010602011771号