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


    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
  • 相关阅读:
    SqlServer相关文章
    C#字典(Dictionary)相关文章记录
    Mysql相关文章
    Rabbitmq相关文章
    C#多线程之锁相关文章
    23种设计模式相关文章
    IdentityServer4之Consent与offline_access
    .netCore mvc 在javascript中读取属性中文乱码的问题
    基于 NET5的QQ扫码登录的第三方实现(无需注册开发者)
    Windows 操作系统开启长路径支持
  • 原文地址:https://www.cnblogs.com/zahngyu/p/11360320.html
Copyright © 2020-2023  润新知