• 并发编程


    协程:
    单线程下实现并发 并发 = 切换 + 保存状态
    1.遇到IO切, 提高效率
    2.遇到计算切, 并没有提高效率
    1.协程本质:
    协程的本质就是在单线程下,由用户自己控制一个任务遇到io阻塞了就切换另外一个任务去执行,以此来提升效率。
    为了实现它,我们需要找寻一种可以同时满足以下条件的解决方案:
    1. 可以控制多个任务之间的切换,切换之前将任务的状态保存下来,以便重新运行时,可以基于暂停的位置继续执行。
    2. 作为1的补充:可以检测io操作,在遇到io操作的情况下才发生切换
    2.强调:
    1. python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行)
    2. 单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(!!!非io操作的切换与效率无关)
    3.优点:
    1. 协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级
    2. 单线程内就可以实现并发的效果,最大限度地利用cpu
    4.缺点:
    1. 协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程
    2. 协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程
    5.总结:
    1.必须在只有一个单线程里实现并发
    2.修改共享数据不需加锁
    3.用户程序里自己保存多个控制流的上下文栈
    4.附加:一个协程遇到IO操作自动切换到其它协程(如何实现检测IO,yield、greenlet都无法实现,就用到了gevent模块(select机制))

    单线程下实现并发:
    1.yield # yield 遇到io 不能监测到 切换 # 没有io 去切换,会降低执行速度
    2.greenlet 模块 # greenlet 遇到io 不能监测到 切换 # 没有io 去切换,会降低执行速度
    pip3 install greenlet
    greenlet 比yield 好 但是还是不好 遇到io不会切
    g1 = greenlet(eat)
    g1.switch('alice')
    g1.switch()
    3.gevent 模块
    pip3 install gevent
    gevent:封装了greenlet模块,但是他能检测到io 自动切
    加了补丁:from gevent import monkey;monkey.patch_all()
    遇到time.sleep(2) 才会切,否则只有遇到gevent.sleep(2) 才会切,所以必须加补丁
    g1 = gevent.spawn(eat,'alice')
    g1.join() # 等待g1结束
    gevent.joinall([g1,g2])
    g1.value # 拿到func1的返回值

    补丁说明:
    from gevent import monkey;monkey.patch_all()
    1.必须放到被打补丁者的前面,如time,socket模块之前
    2.要用gevent,需要将from gevent import monkey;monkey.patch_all()放到文件的开头

    # 通过gevent实现单线程下的socket并发(from gevent import monkey;monkey.patch_all()一定要放到导入socket模块之前,
    否则gevent无法识别socket的阻塞)
     1 import time
     2 def consumer(res):
     3     '''任务1:接收数据,处理数据'''
     4     pass
     5 
     6 def producer():
     7     '''任务2:生产数据'''
     8     res=[]
     9     for i in range(10000000):
    10         res.append(i)
    11     return res
    12 
    13 start=time.time()
    14 #串行执行
    15 res=producer()
    16 consumer(res) #写成consumer(producer())会降低执行效率
    17 # consumer(producer())
    18 stop=time.time()
    19 print(stop-start) #1.5536692142486572
    计算型的切换降低运行效率
     1 import time
     2 def producter():  # 并发的去执行
     3     g = consumer()
     4     next(g)
     5     for i in range(10):
     6         g.send(i)
     7         time.sleep(2)
     8 
     9 def consumer():
    10     while True:
    11         res = yield
    12         print(res)
    13 start = time.time()
    14 producter()
    15 stop = time.time()
    16 print(stop-start)
    17 
    18 import time
    19 def producter():  # 串行  计算型的 切换 会降低运行效率
    20     res = []
    21     for i in range(10000000):
    22         res.append(i)
    23     return res
    24 
    25 def consumer(res):
    26     pass
    27 
    28 start = time.time()
    29 res = producter()  # 串行 执行  写成 consumer(producter()) 会降低执行效率
    30 consumer(res)
    31 stop = time.time()
    32 print(stop-start)
    yield,不能实现遇到io切换
     1 from greenlet import greenlet
     2 import time
     3 
     4 def eat(name):
     5     print('%s eat 1'%name)
     6 
     7     g2.switch('alice')
     8     time.sleep(4)  # 遇到io 不会立即s切
     9     print('%s eat 2'%name)
    10     g2.switch()
    11 
    12 def play(name):
    13     print('%s play 1'%name)
    14     g1.switch()
    15     print('%s play 2'%name)
    16 
    17 g1 = greenlet(eat)
    18 g2 = greenlet(play)
    19 
    20 g1.switch('alice')  # 第一次切 需要传参数
    greenlet模块,不能实现遇到io切换
     1 #顺序执行
     2 import time
     3 def f1():
     4     res=1
     5     for i in range(100000000):
     6         res+=i
     7 
     8 def f2():
     9     res=1
    10     for i in range(100000000):
    11         res*=i
    12 
    13 start=time.time()
    14 f1()
    15 f2()
    16 stop=time.time()
    17 print('run time is %s' %(stop-start)) #10.985628366470337
    18 
    19 #切换
    20 from greenlet import greenlet
    21 import time
    22 def f1():
    23     res=1
    24     for i in range(100000000):
    25         res+=i
    26         g2.switch()
    27 
    28 def f2():
    29     res=1
    30     for i in range(100000000):
    31         res*=i
    32         g1.switch()
    33 
    34 start=time.time()
    35 g1=greenlet(f1)
    36 g2=greenlet(f2)
    37 g1.switch()
    38 stop=time.time()
    39 print('run time is %s' %(stop-start)) # 52.763017892837524
    计算型的使用greenlet,单纯的切换会降低运行效率
     1 from gevent import monkey;monkey.patch_all()
     2 import gevent
     3 import time
     4 
     5 def eat(name):
     6     print('%s eat 1'%name)
     7     time.sleep(2)
     8     # gevent.sleep(2)
     9     print('%s eat 2'%name)
    10 
    11 def play(name):
    12     print('%s play 1'%name)
    13     # gevent.sleep(1)
    14     time.sleep(1)
    15     print('%s play 2'%name)
    16 
    17 start = time.time()
    18 g1 = gevent.spawn(eat,'alice')
    19 g2 = gevent.spawn(play,'alice')
    20 # g1.join()
    21 # g2.join()
    22 gevent.joinall([g1,g2])
    23 stop = time.time()
    24 print('',stop-start)
    25 """
    26 alice eat 1  # 遇到 time.sleep() io 并不会切  加个补丁才会切
    27 alice eat 2
    28 alice play 1
    29 alice play 2
    30 主 3.000598430633545
    31 """
    32 """
    33 alice eat 1   # 加了补丁后,可识别time.sleep() io 会切
    34 alice play 1  # 补丁:from gevent import monkey;monkey.patch_all()
    35 alice play 2
    36 alice eat 2
    37 主 2.000128984451294
    38 """
    39 """
    40 alice eat 1   # 遇到 gevent.sleep() 会切
    41 alice play 1
    42 alice play 2
    43 alice eat 2
    44 主 2.0026285648345947
    45 """
    gevent模块,可以实现遇到io切换
     1 # -*- coding:utf-8 -*-
     2 from gevent import spawn,monkey;monkey.patch_all()
     3 import socket
     4 
     5 #如果不想用money.patch_all()打补丁,可以用gevent自带的socket
     6 # from gevent import socket
     7 # s=socket.socket()
     8 
     9 def talk(conn):
    10     while True:
    11         try:
    12             res = conn.recv(1024)
    13             if not res:break
    14             conn.send(res.upper())
    15         except Exception as e:
    16             break
    17     conn.close()
    18 
    19 def server(ip,port):
    20     server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    21     server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    22     server.bind((ip,port))
    23     server.listen(5)
    24     while True:
    25         conn, addr = server.accept()
    26         spawn(talk,conn)
    27 
    28 if __name__ == '__main__':
    29     g = spawn(server,'127.0.0.1',8080)
    30     g.join()
    gevent实现单线程下的socket并发 server
     1 # -*- coding:utf-8 -*-
     2 import socket
     3 from threading import Thread,currentThread
     4 
     5 def client(ip,port):
     6     c = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
     7     c.connect((ip,port))
     8 
     9     while True:
    10         c.send(('%s hello '%currentThread().getName()).encode('utf-8'))
    11         data = c.recv(1024)
    12         print(data.decode('utf-8'))
    13 
    14 if __name__ == "__main__":
    15     for i in range(500):
    16         t = Thread(target=client,args=('127.0.0.1',8080))
    17         t.start()
    gevent实现单线程下的socket并发 client




  • 相关阅读:
    Python统计excel表格中文本的词频,生成词云图片
    springboot application.properties 常用完整版配置信息
    JAVA高级-面试题总结
    删除csdn上面自己上传的资源
    本博客背景特效源码
    我的自定义框架 || 基于Spring Boot || 第一步
    PYTHON 实现的微信跳一跳【辅助工具】仅作学习
    PM2守护babel-node
    记一个HOST引起的前端项目打不开的问题
    迭代器与iterable
  • 原文地址:https://www.cnblogs.com/alice-bj/p/8719176.html
Copyright © 2020-2023  润新知