并发编程之进程池,线程池 和 异步回调,协程

1.进程池和线程池

 

2.异步回调

3.协程

4.基于TCP使用多线程实现高并发

 

一.进程池和线程池

什么是进程池和线程池:

'''
池 Pool 指的是一个容器
线程池就是用来存储线程对象的 容器

创建池子 可以指定池子里面有多少线程,如果不指定就默认为CUP个数*5
不会立即开启线程,会等到有任务提交后在开启线程

 线程池,不仅帮我们管理了线程的开启和销毁,还帮我们管理任务的分配
    特点: 线程池中的线程只要开启之后 即使任务结束也不会立即结束  因为后续可能会有新任务
          避免了频繁开启和销毁线程造成的资源浪费
    1.创建一个线程池
    2.使用submit提交任务到池子中   ,线程池会自己为任务分配线程

进程池与线程池:用法都是一样的
那么为什么要使用池子:
        因为使用池子可以限制并发的任务数目,在计算机可以承受的范围内去并发执行任务


那么既然提到了提交任务,那提交任务分为哪几种
    同步:提交任务之后 原地等待任务的返回结果,期间不做任何事
    异步:提交任务之后 不等待任务的返回结果(异步的结果怎么拿:通过异步回调拿) 直接执行下一行代码

注意:
    池子中创建的进程和线程创建一个就不会创建了,
    至始至终使用的都是最初的那几个,这样话会节省
    开辟进程和线程的资源
'''

案例:使用代码实现线程池

 1 # 线程池
 2 from concurrent.futures import ThreadPoolExecutor
 3 import time
 4 import os
 5 
 6 pool = ThreadPoolExecutor(5)# 括号内可以传参数指定线程池内的线程个数
 7                             # 也可以不传,不传默认是当前所在计算机的cpu个数*5
 8 
 9 def task(n):
10     print(n,os.getpid())# 查看当前线程号 为了验证线程创建就不会变了
11     time.sleep(2)
12 
13 for i in range(20):
14     pool.submit(task,i)# 朝池子中提交任务  属于异步提交
15     print('')
View Code

 

案例:使用代码实现进程池

 1 # 进程池
 2 from concurrent.futures import ProcessPoolExecutor
 3 import time
 4 import os
 5 
 6 pool = ProcessPoolExecutor()#不传默认是计算机cpu的个数
 7 
 8 def task(n):
 9     print(n,os.getpid())# 查看当前进程号
10     time.sleep(2)
11 
12 if __name__ == '__main__':
13 
14     for i in range(20):
15         pool.submit(task,i)# 朝池子中提交任务 属于异步提交
16         print('')
View Code

 

二.异步回调

那么如何通过异步回调拿到结果:

如何通过异步回调拿到结果
 异步回调
    异步指的是任务的提交方式是异步的

异步任务的问题:
​        如果这个任务执行完成后会产生返回值,任务发起方该何时去获取结果

解决方案:
​        异步回调
异步回调指的就是一个函数,该函数会在任务后自动被调用,并且会传入Future对象 ,
通过Future对象的result()获取执行结果 ,
有了回调函数 就可以在任务完成时 及时处理它

 

第一种方式:

 1 # 第一种方式
 2 # 线程池和进程池
 3 from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
 4 import time
 5 import os
 6 
 7 # pool = ThreadPoolExecutor(5)# 括号内可以传参数指定线程池内的线程个数
 8                             # 也可以不传,不传默认是当前所在计算机的cpu个数*5
 9 
10 pool = ProcessPoolExecutor()# 不传默认就是计算机cpu个数
11 
12 def task(n):
13     print(n,os.getpid())# 查看当前进程号 为了验证线程创建就不会变了
14     time.sleep(2)
15     return n **2# 返回值
16 
17 if __name__ == '__main__':
18 
19     t_list = []# 先一次性把所有的线程都起来放入这个列表里
20     for i in range(20):
21 
22         res = pool.submit(task,i)# 朝池子中提交任务  属于异步提交
23         # print(res.result())# 原地等待任务的返回结果,这又变成了同步
24         t_list.append(res)
25 
26     pool.shutdown()# 关闭池子,等待池子中所有的任务执行完毕之后,才会往下运行代码
27                      # 这个可以让20 个线程先运行结束在拿结果
28     for p in t_list:
29         print('>>>',p.result())# 通过返回值点result拿结果
View Code

 

注意:但是第一种解决方案是有问题的

之前说过异步提交这个结果,一旦有结果就会有对应的机制取处理他,
但是上面的第一种方式并没有处理,上面那种方式只是用for 循环然后人为的认为
第一个就是他的结果第二个就是下一个的结果

最终版本异步回调:

 1 # 线程池和进程池
 2 from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
 3 import time
 4 import os
 5 
 6 # pool = ThreadPoolExecutor(5)# 括号内可以传参数指定线程池内的线程个数
 7                             # 也可以不传,不传默认是当前所在计算机的cpu个数*5
 8 
 9 pool = ProcessPoolExecutor()# 不传默认就是计算机cpu个数
10 
11 def task(n):
12     print(n,os.getpid())# 查看当前进程号 为了验证线程创建就不会变了
13     time.sleep(2)
14     return n **2# 返回值
15 
16 def call_back(x):
17     print('拿到了异步提交任务的返回结果:',x.result())
18 #异步回调机制:当异步提交的任务有返回结果之后,会自动触发回调函数的执行
19 if __name__ == '__main__':
20 
21     t_list = []# 先一次性把所有的线程都起来放入这个列表里
22     for i in range(20):
23 
24         res = pool.submit(task,i).add_done_callback(call_back)# 提交任务的时候,给每个任务都绑定一个回调函数
25                                                                 # 一旦该任务有结果,就会立刻执行对应的回调函数
26         t_list.append(res)
View Code

 

三.协程

'''
协程
    进程:资源单位
    线程:执行单位
    协程:单线程下实现并发

并发是什么:
    切换+保存状态:看起来像同时执行的,就可以称之为并发

协程:完全就是程序员自己定义出来的名词,就是单线程下实现并发

那么并发需要哪些条件:
    多道技术
        空间上的复用
        时间上的复用
            切换+保存状态

程序员自己通过代码自己检测程序中的IO
一旦遇到IO自己通过代码切换
给操作系统的感觉是你这个线程没有任何的IO
ps:欺骗操作系统 让它误认为你这个程序一直没有IO
从而保证程序在运行态和就绪态来回切换
提升代码的运行效率

切换+保存状态就一定能够提升效率吗???
当你的任务是iO密集型的情况下  提升效率
如果你的任务是计算密集型的   降低效率

所以需要找到一个能够识别IO的一个工具
gevent模块
那么因为gevent模块没有办法自动识别time.sleep等io请况,需要自己配置一个参数
需要注意:
1.如果主线程结束了 协程任务也会立即结束。
2.monkey补丁的原理是把原始的阻塞方法替换为修改后的非阻塞方法,即偷梁换柱,来实现IO自动切换
​必须在打补丁后再使用相应的功能,避免忘记,建议写在最上方
'''

案例:

 1 from gevent import monkey;monkey.patch_all() # 由于该模块经常被使用,所以建议写在一行
 2 from gevent import spawn
 3 import time
 4 
 5 def heng():
 6     print("")
 7     time.sleep(2)
 8     print('')
 9 
10 def ha():
11     print('')
12     time.sleep(3)
13     print('')
14 
15 def heiheihei():
16     print('嘿嘿嘿')
17     time.sleep(5)
18     print('嘿嘿嘿')
19 
20 
21 start = time.time()
22 g1 = spawn(heng)# spawn会检测所有的任务
23 g2 = spawn(ha)
24 g3 = spawn(heiheihei)
25 
26 g1.join()
27 g2.join()# 如果没有join直接就结束了,因为主线程都结束了
28 g3.join()
29 print(time.time() - start)
View Code

 

TCP单线程实现并发:

服务端:

 1 from gevent import monkey;monkey.patch_all()
 2 import socket
 3 from gevent import spawn
 4 
 5 server = socket.socket()
 6 server.bind(('127.0.0.1',8080))
 7 server.listen(5)
 8 
 9 def talk(conn):
10     while True:
11         try:
12             data = conn.recv(1024)
13             if not data:break
14             print(data.decode('utf-8'))
15             conn.send(data.upper())
16         except ConnectionResetError as e:
17             print(e)
18             break
19     conn.close()
20 
21 def server1():
22     while True:
23         conn,addr = server.accept()
24         spawn(talk,conn)# 监测和调用这个函数
25 
26 if __name__ == '__main__':
27     g1 = spawn(server1)# 也是监测和调用
28     g1.join()
View Code

客户端:

 1 import socket
 2 from threading import Thread,current_thread
 3 
 4 
 5 def client():
 6     client = socket.socket()
 7     client.connect(('127.0.0.1',8080))
 8     n = 0
 9     while True:
10 
11         data = '%s %s'%(current_thread().name,n)
12         client.send(data.encode('utf-8'))
13         res = client.recv(1024)
14         print(res.decode('utf-8'))
15         n += 1
16 
17 
18 for i in range(400):
19     t = Thread(target=client)
20     t.start()
View Code

 

四.基于TCP使用多线程实现高并发

'''
服务端
1 要有固定的ip和port
2 24小时不间断提供服务
3 能够支持并发

TCP服务端实现并发
1 将不同的功能尽量拆分成不同的函数,
拆分出来的功能可以被多个地方使用

1 将连接循环和通信循环拆分成不同的函数
2.将通信循环做成多线程

服务端:

 1 import socket
 2 from threading import Thread
 3 
 4 server = socket.socket()
 5 server.bind(('127.0.0.1',8080))
 6 server.listen(5)
 7 
 8 def talk(conn):
 9     while True:
10         try:
11             data = conn.recv(1024)
12             if not data:break
13             print(data.decode('utf-8'))
14             conn.send(data.upper())
15         except ConnectionResetError as e:
16             print(e)
17             break
18     conn.close()
19 
20 
21 while True:
22     conn,addr = server.accept()# 监听,等待客户端的连接,阻塞态
23     t = Thread(target=talk,args=(conn,))
24     t.start()
View Code

客户端:

1 import socket
2 client = socket.socket()
3 client.connect(('127.0.0.1',8080))
4 while True:
5     msg = input('msg:')
6     if not msg:continue
7     client.send(msg.encode('utf-8'))
8     client.recv(1024)
9     client.close()
View Code

 

但是以上案例是有一个致命的缺陷的:

但是用这种方法有很大的缺点,就是万一有成千上万的客户端来连,
就需要开启对应的线程,就算线程开销再小,也是有消耗的,
这样会导致内存被占满,计算机崩溃,需要用线程池才可以解决问题

解决方案就是使用线程池:

服务端:

 1 import socket
 2 from concurrent.futures import ThreadPoolExecutor
 3 tpool = ThreadPoolExecutor(3)
 4 
 5 def communicate(conn):
 6     while True:
 7         try:
 8             data = conn.recv(1024)
 9             if not data: break
10             conn.send(data.upper())
11         except ConnectionResetError as e:
12             print(e)
13             break
14     conn.close()
15 
16 def server():
17     server = socket.socket()
18     server.bind(('127.0.0.1',8080))
19     server.listen(5)
20 
21     while True:
22         conn,addr = server.accept()
23         print(addr)
24         tpool.submit(communicate,conn)
25 
26 if __name__ == '__main__':
27     server()
View Code

客户端:

 1 import socket
 2 client=socket.socket()
 3 client.connect(('127.0.0.1',8080))
 4 
 5 while True:
 6     msg = input('msg>>>').strip()
 7     if msg == 'q':break
 8     if not msg:continue
 9     client.send(msg.encode('utf-8'))
10     data = client.recv(1024)
11     print(data.decode('utf-8'))
12 
13 client.close()
View Code

 

posted @ 2019-08-15 20:21  ZHANGYUZY  阅读(409)  评论(0编辑  收藏  举报