并发编程之协程

hamutailang 2019-11-03

什么是协程?
协程:是单线程下的并发,又称为微线程,纤程。协程是由用户程序自身控制的。
ps:1、python的线程属于内核级别的,是由操作系统调度
   2、单线程内开启协程,一旦遇到io,就会从应用程序级别控制切换,而不是由操作系统来进行切换,(如果不是io操作而进行切换,并不会提升效率)
协程的优点:
1、协程的开销更小,是属于程序级别的切换,操作系统完全感知不到。
2、在单线程下便可以实现并发的效果,最大限度的利用cpu
缺点:
1、无法利用到多核(多个cpu)的优势,要想利用,可以在程序中开启多个进程,在每个进程中开启多个线程,在线程中开启多个携程
2、协程指的是单个线程,所以一旦协程出现了阻塞,那么便会导致    整个线程被阻塞。

greenlet模块
使用greenlet模块可以很轻松的实现多个任务之间进行来回的切换

from greenlet import greenlet


def make_girlfriend(name):
    print("%s is making old girlfriend" % name)
    g2.switch(name)
    print("%s is making new girlfriend" % name)
    g2.switch()

def play_computer_game(name):
    print("%s is playing lol" % name)
    g1.switch()
    print("%s is playing cf" % name)
    g1.switch()

g1 = greenlet(make_girlfriend)
g2 = greenlet(play_computer_game)

g1.switch("xu") # 传了一次参数过后,下一次在switch的时候便不需要在传递参数了
‘‘‘
打印结果:
xu is making old girlfriend
xu is playing lol
xu is making new girlfriend
xu is playing cf
‘‘‘

但是greenlet模块只是进行了对任务的单纯的切换,在遇到了io阻塞时,还是会原地的阻塞住,并没有在io阻塞时对cpu进行调度。
在单线程里多个任务中,并且任务中含有io阻塞,要想在阻塞状态时,对cpu进行调度,便需要使用到gevent模块

gevent模块
pip3 install gevent 对gevent模块进行安装
gevent模块的使用方法
g1 = gevent.spawn(函数名,函数参数...) # 创建gevent对象
g2 = gevent.spawn(函数名,函数参数...) # 创建gevent对象
g1.join() # 等待g1结束
g2.join() # 等待g2结束
或者 gevent.joinall(g1, g2)
g1.value():拿到函数的返回值

gevent遇到阻塞时会自动进行任务切换

import gevent

def make_girlfriend(name):
    print("%s is making old girlfriend" % name)
    gevent.sleep(2)
    print("%s is making new girlfriend" % name)


def play_computer_game(name):
    print("%s is playing lol" %name)
    gevent.sleep(2)
    print("%s is playing cf" % name)

g1 = gevent.spawn(make_girlfriend,"xu")
g2 = gevent.spawn(play_computer_game,"xu")

gevent.joinall([g1,g2])
‘‘‘
打印结果:
xu is making old girlfriend
xu is playing lol
xu is making new girlfriend
xu is playing cf
‘‘‘

但是上面的只能捕捉到gevent的阻塞,并不能对其他io阻塞进行捕捉,这时就需要打补丁

from gevent import monkey;monkey.patch_all() # 打补丁,必须放在最前面
import gevent
import time


def make_girlfriend(name):
    print("%s is making old girlfriend" % name)
    time.sleep(2)
    print("%s is making new girlfriend" % name)


def play_computer_game(name):
    print("%s is playing lol" %name)
    time.sleep(2)
    print("%s is playing cf" % name)

start_time = time.time()
g1 = gevent.spawn(make_girlfriend,"xu")
g2 = gevent.spawn(play_computer_game,"xu")

gevent.joinall([g1,g2])

consumption_time = time.time() - start_time
print(consumption_time)
‘‘‘
打印结果:
xu is making old girlfriend
xu is playing lol
xu is making new girlfriend
xu is playing cf
2.004627227783203
‘‘‘

由此可以看出,当任务遇到io阻塞后,便会切换到其他任务,再遇到io阻塞时,便会再次切换。这样便实现了看起来并发的作用,对cpu的利用效率也提高了。

练习:通过协程实现套接字通信

服务端
from gevent import monkey;monkey.patch_all()
import gevent
import socket

def server(sever_ip, port):
    sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sk.bind((sever_ip, port))
    sk.listen(5)
    while True:
        conn, addr = sk.accept()
        gevent.spawn(connect_user, conn)


def connect_user(conn):
    while True:
        try:
            data = conn.recv(1024)
            if not data:break
            send_data = data.upper()
            conn.send(send_data)
        except ConnectionError as e:
            conn.close()
            break

if __name__ == ‘__main__‘:
    server("127.0.0.1",8080)

客户端
import socket

def client(server_ip, port):
    conn= socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    conn.connect((server_ip, port))

    while True:
        data = input(">>>:").strip()
        if data == "q":break
        conn.send(bytes(data, ‘utf-8‘))
        recv_data = conn.recv(1024)
        print(str(recv_data, "utf-8"))
    conn.close()


if __name__ == ‘__main__‘:
    client(‘127.0.0.1‘, 8080)

相关推荐

fanhuasijin / 0评论 2019-12-17