网络编程之:协程


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

协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。

优点:

  • 无需线程上下文切换的开销
  • 无需原子操作锁定及同步的开销
    • “原子操作(atomic operation)是不需要synchronized”,所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序是不可以被打乱,或者切割掉只执行部分。视作整体是原子性的核心。
  • 方便切换控制流,简化编程模型
  • 高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。

缺点:

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

符合什么条件就能称之为协程:

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

1 Greenlet

greenlet是一个用C实现的协程模块,相比与python自带的yield,它可以使你在任意函数之间随意切换,而不需把这个函数先声明为generator

from greenlet import greenlet


def test1():
    print(12)
    gr2.switch()
    print(34)
    gr2.switch()

def test2():
    print(56)
    gr1.switch()
    print(78)
    gr1.switch()


gr1 = greenlet(test1)  # 生成一个协程
gr2 = greenlet(test2)
gr1.switch()

# 输出结果:
12
56
34
78

2 Gevent

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

2.1 协程间自动切换

import gevent

def Foo():
    print('runnig Foo')
    gevent.sleep(2)
    print('explicit running in Foo again')

def Bar():
    print('running Bar')
    gevent.sleep(1)
    print('explicit running in Bar again')

def Fun():
    print('running Fun')
    gevent.sleep(0)  # gevent.sleep()触发切换
    print('explicit running in Fun again')


gevent.joinall([
    gevent.spawn(Foo),
    gevent.spawn(Bar),
    gevent.spawn(Fun),
])  # 列表形式,产生3个协程,实现协程之间自动切换

# 输出:
runnig Foo
running Bar
running Fun
explicit running in Fun again
explicit running in Bar again
explicit running in Foo again

2.2 同步与异步的性能区别

异步遇到IO阻塞时会自动切换任务

from urllib import request
import gevent,time

from gevent import monkey
monkey.patch_all()  # 默认urllib无法与gevent协同,需要打个补丁,将当前程序所有I/O操作打标记



def Fun(url):
    print('GET: %s' % url)
    req = request.urlopen(url)
    data = req.read()
    print('%d bytes receied from %s.' % (len(data), url))

urls = ['https://www.python.org',
        'https://www.yahoo.com',
        'https://github.com'
        ]

time_start = time.time()
for url in urls:
    Fun(url)

print('同步cost', time.time() - time_start)

async_time_start = time.time()
gevent.joinall([
    gevent.spawn(Fun, 'https://www.python.org'),
    gevent.spawn(Fun, 'https://www.yahoo.com'),
    gevent.spawn(Fun, 'https://github.com'),
])

print("异步cost",time.time() - async_time_start)

2.3 实现单线程下的多socket并发

server端

import socket
import gevent

from gevent import monkey
monkey.patch_all()


def server(port):
    ser = socket.socket()
    ser.bind(('localhost', port))
    ser.listen(1024)
    while True:
        conn, addr = ser.accept()
        gevent.spawn(handle_request, conn)

def handle_request(conn):
    try:
        while True:
            data = conn.recv(1024)
            print('rece: ', data)
            conn.send(data.upper())
            if not data:
                conn.shutdown(socket.SHUT_WR)

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

if __name__ == '__main__':
    server(6666)

client端

#单次连接
import socket
HOST = 'localhost'
PORT = 6666
cli = socket.socket()
cli.connect((HOST, PORT))

while True:
    msg = input('>>: ').strip().encode('utf-8')
    if len(msg) == 0: continue
    cli.send(msg)
    data = cli.recv(1024)
    print(data.decode('utf-8'))


# 并发
import socket
import threading

HOST = 'localhost'
PORT = 6666

def sock_conn():

    client = socket.socket()
    client.connect((HOST, PORT))
    client.send( ("hello %s" %count).encode("utf-8"))
    data = client.recv(1024)

    print("[%s]recv from server:" % threading.get_ident(),data.decode()) #结果
    client.close()


for i in range(100):
    t = threading.Thread(target=sock_conn)
    t.start()
posted @ 2020-11-08 10:38  f_carey  阅读(11)  评论(0编辑  收藏  举报  来源