python-协程

概念

1.进程(同步)

进程是系统进行资源分配和调度的一个独立单位。每个进程都有自己的独立内存空间,不同进程通过进程间通信来通信。

上下文进程间的切换开销,比较大,但相对比较稳定安全

2.线程(同步)

线程是CPU调度和分派的基本单位。

线程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据。

3.协程(异步)

**协程是一种用户态的轻量级线程,**协程的调度完全由用户控制

“协程就是你可以暂停执行的函数”。协程拥有自己的寄存器上下文和栈。

非抢占式多任务

协程与线程的区别:
  1. 一个线程可以多个协程,一个进程也可以单独拥有多个协程。

  2. 线程进程都是同步机制,而协程则是异步

  3. 协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态。

4)线程是抢占式,而协程是非抢占式的,所以需要用户自己释放使用权来切换到其他协程,因此同一时间其实只有一个协程拥有运行权,相当于单线程的能力。

5)协程并不是取代线程, 而且抽象于线程之上, 线程是被分割的CPU资源, 协程是组织好的代码流程, 协程需要线程来承载运行, 线程是协程的资源, 但协程不会直接使用线程, 协程直接利用的是执行器(Interceptor), 执行器可以关联任意线程或线程池, 可以使当前线程, UI线程, 或新建新程.。

6)线程是协程的资源。协程通过Interceptor来间接使用线程这个资源。

代码

greenlet

 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
33
34
35
36
37
38
# 协程   =>又叫:非抢占式多任务
# 中断执行
# 线程
"""
pip install greenlet
"""

# 基本使用
from greenlet import greenlet
import random
import time


def Producer():
    while True:
        item = random.randint(0, 99)
        print("pro生产了{}".format(item))
        c.switch(item)   # 暂停当前协程,切换到(执行的协程)消费者,并将item传入消费者
        time.sleep(1)

def Consumer():
    while True:
        item = p.switch()    # 切换到生产者, 并等待消费者传入item
        print("消费了{}".format(item))

c = greenlet(Consumer)   # 将一个普通函数变成协程
p = greenlet(Producer)
c.switch()      # 让消费者先进入暂停状态, (只有恢复的时候才能接受数据)




"""
# greenlet 的价值
价值一: 高性能的原生协程
价值二: 语义更加明确的显式切换
价值三: 直接将函数包装成协程,保持原有代码风格
"""

gevent

gevent是一个基于协程的python网络库,在遇到IO阻塞时,程序会自动进行切换,可以让我们用同步的方式写异步IO代码。

 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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# gevent   
# 当一个greenlet遇到IO操作,比如访问网络,就会自动切换,直到IO操作完成,再切换回来
# 遇到阻塞就切换到另一个协程继续执行 !
"""
pip install gevent

gevent,通过封装了 libev(基于epoll) 和 greenlet 两个库。
帮我们做好封装,允许我们以类似于线程的方式使用协程。
以至于我们几乎不用重写原来的代码就能充分利用 epoll 和 协程 威力
"""

import gevent
from gevent.queue import Queue

#  猴子补丁   monkey    将一些阻塞的模块动态的修改为非阻塞
from gevent import monkey;monkey.patch_all()  # 不会阻塞了就
from gevent import monkey;monkey.patch_socket()  # 不会阻塞了就
import requests
import gevent

def get_response():
    print("start")
    requests.get("https://www.baidu.com")
    print("end")

tasks = [gevent.spawn(get_response) for i in range(20)] # 创建协程
gevent.joinall(tasks)    # 阻塞等待协程完毕

# ---------------------------------------------------------------------

### gevent 并发服务器
import gevent
# 将python内置的socket直接换成了IO多路复用的socket
from gevent import monkey;monkey.patch_socket()
import socket

server = socket.socket()
server.bind(('127.0.0.1', 8888))
server.listen(1000)

def worker_coroutine(conn):
    while True:
        recv_data = conn.recv(1000)
        if recv_data:
            print(recv_data)
            conn.send(recv_data)
        else:
            conn.close()
            break

if __name__ == '__main__':
    while True:
        conn, remote_addr = server.accept()
        # 生成一个协程, 并将conn作为参数传入
        gevent.spawn(worker_coroutine, conn)  


# ---------------------------------------------------------------------

# gevent 协程通信
"""
问题一: 协程之间不是能通过switch通信嘛?
是的,由于 gevent 基于 greenlet,所以可以。

问题二: 那为什么还要考虑通信问题?
因为 gevent 不需要我们使用手动切换,
而是遇到阻塞就切换,因此我们不会去使用switch !
"""

import gevent
from gevent import monkey;monkey.patch_all()
from gevent.queue import Queue
import random

queue = Queue(3)

def Producer():
    while True:
        item = random.randint(0, 99)
        print("生产了{}".format(item))
        queue.put(item)

def Consumer():
    while True:
        item = queue.get()
        print("消费了{}".format(item))


p = gevent.spawn(Producer, queue)  # 将函数封装成协程, 并开始调度
c = gevent.spawn(Consumer, queue)

# gevent.sleep(5)
gevent.joinall([p, c])    # 阻塞(一阻塞就切换协程) 等待   等待你带进来的所有协程对象结束

Note:

1
2
3
4
# 这三句一定要放在最上面
import gevent
from gevent import monkey
monkey.patch_all()  
0%