python运维——协程及其实现

协程

首先要明确,线程和进程都是系统帮咱们开辟的,不管是thread还是process他内部都是调用的系统的API,而对于协程来说它和系统毫无关系;

协程不同于线程的是,线程是抢占式的调度,而协程是协同式的调度,也就是说,协程需要自己做调度。

他就和程序员有关系,对于线程和进程来说,调度是由CPU来决定调度的;

对于协程来说,程序员就是上帝,你想让谁执行到哪里他就执行到哪里;

协程存在的意义:对于多线程应用,CPU通过切片的方式来切换线程间的执行,线程切换时需要耗时(保存状态,下次继续)。协程,则只使用一个线程,在一个线程中规定某个代码块执行顺序。(当然协程也可以配合多线程实现异步)

适用场景:其实在其他语言中,协程的其实是意义不大的多线程即可已解决I/O的问题,但是在python因为他有GIL(Global Interpreter Lock 全局解释器锁 )在同一时间只有一个线程在工作,所以:如果一个线程里面I/O操作特别多,协程就比较适用;

 

协程,又称微线程,纤程。英文名Coroutine。一句话说明什么是线程:协程是一种用户态的轻量级线程

协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:

协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。

 

协程的好处:

  • 无需线程上下文切换的开销
  • 无需原子操作锁定及同步的开销
  • 方便切换控制流,简化编程模型
  • 高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。

缺点:

  • 无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。
  • 进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序

一:使用yield实现协程操作例子

#!/usr/bin/env python3
#yield实现协程

import time,queue

def consumer(name):
    print("开始吃包子。。。")
    while True:
        new_baozi = yield #函数暂时在这里停止
        print("%s 开始吃包子 %s " % (name, new_baozi))


def producer():
    c1 = con.__next__() #consumer通过yield变成了迭代器,要通过__next__方法来执行
    c2 = con2.__next__()
    n = 0
    while n < 5:
        n += 1
        con.send(n) #向con中的yield发送n,即把函数中的yield换成n
        con2.send(n)
        print("制作了 包子 %s " % n)

if __name__ == "__main__":
    con = consumer('lalala')
    con2 = consumer('hahaha')
    producer()
开始吃包子。。。
开始吃包子。。。
lalala 开始吃包子 1 
hahaha 开始吃包子 1 
制作了 包子 1 
lalala 开始吃包子 2 
hahaha 开始吃包子 2 
制作了 包子 2 
lalala 开始吃包子 3 
hahaha 开始吃包子 3 
制作了 包子 3 
lalala 开始吃包子 4 
hahaha 开始吃包子 4 
制作了 包子 4 
lalala 开始吃包子 5 
hahaha 开始吃包子 5 

制作了 包子 5 

  • 协程的定义: 
    • 必须只有在单个线程里实现并发
    • 修改共享数据不需加锁
    • 用户程序里自己保存多个控制流上的上下文
    • 一个协程遇到IO操作自动切换到其他协程

greenlet

greenlet是用C实现的协程模块,相比与python自带的yield,他可以使你在任意函数之随意切换。


#!/usr/bin/env python3
#通过greenlet来实现协程

from greenlet import greenlet

def test1():
    print(12)
    gr2.switch() #在这里实现跳转,转到test2
    print(34)
    gr2.switch()

def test2():
    print(56)
    gr1.switch() #在这里实现跳转,转到test1
    print(78)

gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()

gevent

gevent是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是greenlet,它是以C扩展模块形式介入Python的轻量级协程。在greenlet全部运行在主程序操作系统进程的内部,但它们被协作式调度。

#!/usr/bin/env python3
#通过gevent来实现协程

import gevent

def func1():
    print(" the fun1 start...")
    gevent.sleep(2) #遇到堵塞就跳转(根据堵塞的时间长短)
    print(' the fun1 end...')

def func2():
    print("\033[31;1m the func2 start \033[0m")
    gevent.sleep(1)
    print('\033[31;1m the func2 end \033[0m')

def func3():
    print("\033[32;1m the func3 start \033[0m")
    gevent.sleep(5)
    print('\033[32;1m the func3 end \033[0m')

def func4():
    print("\033[33;1m the func4 start \033[0m")
    gevent.sleep(4)
    print('\033[33;1m the func4 end \033[0m')

gevent.joinall(
    [
        gevent.spawn(func1),
        gevent.spawn(func2),
        gevent.spawn(func3),
        gevent.spawn(func4),
    ]
)

同步可异步的性能区别

#!/usr/bin/env python3
#同步和异步性能的区分
import gevent

def tasf(n):
    gevent.sleep(1)
    print("The func print %s" % n)

def tongbu():
    for i in range(10):
        tasf(i)

def yibu():
    threads = [gevent.spawn(tasf, i) for i in range(10)]
    gevent.joinall(threads)

print('tongbu')
tongbu()

print('yibu')
yibu()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

上面的程序的主要部分时间tasf函数封装到greenlet内部线程的gevent.spawn。初始化的greenlet列表存放在threads列表中,次数组被传给gevent.joinall函数后,后者阻塞当前的流程,并执行所有给定的greenlet。执行流程只会在所有greenlet执行完成后才会继续向下走。

当遇到阻塞是会自动切换任务


#!/usr/bin/env python3
#遇到阻塞是自动区分

from gevent import monkey;monkey.patch_all()
import gevent
from urllib.request import urlopen

def f(url):
    print('GET: %s' % url)
    resp = urlopen(url)
    data = resp.read()
    print('%d bytes received from %s.' % (len(data), url))

gevent.joinall([
    gevent.spawn(f,'https://www.baidu.com'),
    gevent.spawn(f,'https://www.sina.com'),
])

通过gevent实现单线程下的多socket并发

#!/usr/bin/env python3
#通过gevent实现单线程下的多socket并发
#server端
import gevent
from gevent import socket,monkey

monkey.patch_all()#将python标准库中的网络借口不阻塞

def server(port):
    s = socket.socket()
    s.bind(('127.0.0.1',port))
    s.listen(500)
    while True:
        cli, addr = s.accept()
        gevent.spawn(handle_request, cli) #执行handle_request函数,cli是参数

def handle_request(conn):
    try:
        while True:
            data = conn.recv(1024) #接收数据,这里设置成不阻塞
            print("recv:",data)
            conn.send(data)
            if not data:
                conn.shutdown(socket.SHUT_WR) #如果接收为空值,结束

    except Exception as ex:
        print(ex)
    finally:
        conn.close()

if __name__ == '__main__':
    server(8001)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#!/usr/bin/env python3
#通过gevent实现单线程下的多socket并发
#client端
import socket

host = "127.0.0.1"
port = 8001
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host,port))
while True:
    user_input = bytes(input(),encoding = "utf8")
    s.sendall(user_input)
    data = s.recv(1024)
    print(repr(data))
s.close()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
100个并发的socket链接

#!/usr/bin/env python3
#通过gevent实现单线程下的多socket并发
#client端2
import socket
import threading

def sock_conn():
    client = socket.socket()
    client.connect(('127.0.0.1',8001))
    count = 0
    while True:
        client.send(('hello %s % count').encode('utf-8'))
        data = client.recv(1024)
        print('[%s]recv from server:' % threading.get_ident(),data.decode())
        count += 1
    client.close()

for i in range(100):
    t = threading.Thread(target=sock_conn)
    t.start()



猜你喜欢

转载自blog.csdn.net/runner668/article/details/80200091