python 3.x 生成器实现协程

生成器实现协程

在介绍协成之前先了解C10M的问题
随着时代的发展,C10K的并发也不再满足人类的需求,就出现了C10M的问题.

  • C10M问题:
    • 如何利用8核心CPU,64G内存,在10gbs的网络上保持1000万并发连接.  
    • 解决这种问题需要协程技术来解决  
    • 最基本的编码采用的是同步模式,后期介绍select+回调+事件循环的模式,我们采用这种模式来完成如何在单线程中实现并发,这就是回调模式.
      但是回调模式有很多的缺点,协程就是来解决回调模式编写难的一个问题.  
  • 我们要解决的问题就是一步一步将我们的代码变的容易理解的同时还能够让我们的代码的性能很高.
  • 现在所面临的问题:
    • 1.回调模式编码复杂度高.  
    • 2.同步编程的并发性不高  
    • 3.多线程编程需要线程间同步,线程间同步使用的是锁(lock)的机制,锁的机制会降低并发性能.  
  • 为了解决上面的三个问题,最理想的情况(看似简单,实现起来是比较难的一个过程):
    • 1.采用同步的方式编写异步的代码.  
    • 2.使用单线程去切换任务  
  • 带来的挑战是什么?  
    • 1.线程是由操作系统切换的,如果我们自己声明一个线程,操作系统会帮助我们切换任务.那我们现在要在单线程中切换任务,就意味着我们需要程序员自己去调度任务.操作系统不再帮我们完成切换任务.  
    • 2.实现单线程切换任务的好处:  
      • 1.不再需要锁    
        • lock的作用是完成线程间的同步,在一个线程里不断的切换任务,就意味着我们不再需要锁了.      
      • 2.并发性高    
        • 并发性高不光是因为锁的问题(锁的机制会降低并发性能),      
        • 还有一个问题就是线程的代价比较大,新建一个线程对操作系统来说需要的内存比较大的,而且线程间的切换过程比较慢      
  • 如果我们能达到在单线程中切换任务,就像函数之间的调用一样,函数之间的调用的性能是远远高于线程间切换的性能.所以说我们如果实现单线程内切换函数,性能远远高于线程切换,而且并发性更高.
    比如说在内部声明1000个函数比声明1000个线程的切换的性能高很多.对内存的占用会更小.
  • 以上就是我们面临的最棘手的两个问题
    • 线程中切换函数就是一个很大的挑战.  
      • 传统函数调用过程A函数中调用B函数,B函数中调用C函数,每个函数都是有一个栈的,函数只会运行一次后会退出的.退出后的函数就没有关系了.    
      • 我们现在想要采用传统编程的整个过程一路走下来,他是不行的.因为函数一但调用就会等待函数的返回为止.    
  • 我们现在需要一个可以暂停的函数,并且可以在适当的时候恢复该函数的继续执行.因为有这样的一个函数,我们就可以以同步的方式进行编码了.
  • 为了解决可以暂停的函数,并且可以在适当的时候恢复该函数的继续执行这个问题就出现了协程.

协程(两种定义):

  1. 有多个入口的函数
  2. 可以暂停的函数(可以向暂停的地方传入值)(倾向于第二种说法)
    • 可以暂停的函数就是生成器函数,生成器恰好是可以暂停的函数
    • 生成器的特点是可以实现暂停.协程里面很重要的特性就是可以暂停函数.
  • 协程本身并不比事件循环+回调这种编码方式性能高,反而协程的性能会比事件循环+回调的性能低一些.
  • 协程主要解决的问题就是回调之痛的问题,协程可以让事件循环+IO多路复用方法和传统的同步编程模式结合起来.
  • 协程的主要目的是为了解决整个编码习惯的问题

 

应用生成器的一些原理以及方法send(),close(),throw()来实现协程.

  • 生成器的特性:
    • 生成器不仅可以产出值,还可以接收值  
  • 有了send()方法之后,生成器变成协程才有了基础,只有yield语句是无法将生成器变成协程的.
  • 生成器的方法:

    • send()  

    • throw 异常  

    • close 关闭生成器  

 1 def gen_func():
 2     # 获取url的值是一个费时的操作,根据协程的要求,希望走到这一步代码时,可以异步来进行下载任务,然后将执行结果返回
 3     # 希望函数有一个功能可以url传出去,传给另外一个调用者,或者说传递另外一个并发的任务.专门下载html的内容的任务.
 4     # 任务完成后可以将值传回回来,重点是值会传递回来.
 5     # yield "http://projectedu.com" 这样写,yield的返回值,会传递给调用者next()
 6     # 生成器特性: 如果将yield写在赋值表达式的右边的话,可以将在外部拿到生成器对象的所有函数或者说其他的生成器,都可以将值
 7     #            传递到生成器内部
 8     # 这行代码的作用
 9     #   1.可以产出值(表达式的右边产出值:yield "http://projectedu.com")
10     #   2.可以接收值(这个值是指调用方传递进来的值)(表达式的左边接收值,html)
11     html = yield "http://projectedu.com"
12     # 此处的关键是如何将外部函数的返回值,放到生成器内部
13     # 任何一个函数,任何一个地方甚至任何一个模块,只要能够拿到生成器对象,就可以对生成器操作,
14     # 就意味着在任何地方都可以让生成器暂停和恢复
15 
16     print(html)  # 打印结果: jayden 所以说send()方法可以传递值到生成器中.
17     yield 2
18     yield 3
19     return "jayden"
20 
21 
22 if __name__ == '__main__':
23     gen = gen_func()
24     # 启动生成器的方式有两种
25     # 1.next()
26     # 2.send(),这个方法可以将拿到生成器对象的外部函数的值返回给生成器内部.同时还会启动生成器.
27     url = next(gen)
28     # 模拟download url,html内容简化,将"jayden"传递到生成器的内部
29     html_main = "jayden"
30     # send()方法可以将html内容"jayden"传递到生成器的内部.同时还可以重启生成器到下一个yield位置
31     # send()函数的功能包含next()函数的功能,所以在执行send()函数后yield返回值是2
32     print(gen.send(html_main))  # 打印结果: 2  说明send()方法的功能有两个,
33     # 第一将外部函数的值传递给生成器,第二启动生成器执行下一个yield语句
34 
35 """
36 输出结果:
37     jayden
38     2
39 """

 

 1 def gen_func():
 2     html = yield "http://projectedu.com"
 3     print(html)
 4     yield 2
 5     yield 3
 6     return "jayden"
 7 
 8 
 9 if __name__ == '__main__':
10     gen = gen_func()
11     # url = next(gen) 既然send()函数有next()函数的功能,直接将next()函数换成send()函数,来看看什么效果
12     # url = gen.send("python")
13     # 第一个send()函数是将值发送给gen_func()函数中的html对象.但是如果在最开始的时候根本没有执行过生成器的话,就无法运行到
14     # html = yield "http://projectedu.com"这行代码.所以说在send一个非None的值进来的时候就会报错.
15     # 所以说在调用send发送非None值之前,必须启动一次生成器,方式有两种:
16     # 1.next(gen)
17     # 2.gen.send(None)
18     url = gen.send(None)
19     html_main = "jayden"
20     print(gen.send(html_main))
21 
22 """
23 输出结果:
24     执行url = gen.send("python")报错:
25         TypeError: can't send non-None value to a just-started generator
26         意思是在生成器对象刚形成的时候,第一次调用生成器执行send()函数时,不能send一个非None的值.只能send(None)
27     
28     执行url = gen.send(None)输出结果:
29         jayden
30         2
31 """

 

 1 def gen_func():
 2     """
 3     生成器中只有一个yeild语句
 4     """
 5     html = yield "http://projectedu.com"
 6     print(html)
 7     return "jayden"
 8 
 9 
10 if __name__ == '__main__':
11     gen = gen_func()
12     url = gen.send(None)
13     html_main = "jayden"
14     print(gen.send(html_main))
15 
16 """
17 输出结果:
18     抛异常:print(gen.send(html)) ==> StopIteration: jayden
19     因为生成器没有第二个yield语句.所以抛出异常,并把函数的返回值输出.
20 """

 

生成器close()方法,关闭生成器

 1 def gen_func():
 2     """
 3     生成器中只有一个yeild语句
 4     """
 5     yield "http://projectedu.com"
 6     yield 2
 7     yield 3
 8     return "jayden"
 9 
10 
11 if __name__ == '__main__':
12     gen = gen_func()
13     print(next(gen))
14     # 在执行 yield "http://projectedu.com"这行语句的时候.close()方法会抛出有一个异常GeneratorExit.
15     # 注意:
16     #       调用gen.close()语句后,close()方法会在yield "http://projectedu.com"这行语句输出数据之后,
17     #       在下一次运行yield语句之前(yield 2),直接抛出异常GeneratorExit.
18     gen.close()
19     next(gen)
20 
21 """
22 输出结果:
23     File "F:/C/PythonProject/PythonHighLevelAndIo/test/chapter12/coroutine_gen_close_01.py", line 27, in <module>
24         next(gen) --> 在这行语句抛出异常.
25 StopIteration
26 http://projectedu.com
27 """

 

 

 1 def gen_func():
 2     """
 3     生成器中只有一个yeild语句
 4     """
 5     # 把GeneratorExit异常捕获
 6     try:
 7         yield "http://projectedu.com"
 8     except GeneratorExit:
 9         # 此处except后没有任何的操作,只是写了一个pass,有人会说这里什么都没做应该往下执行yield 2语句,为什么会报错呢???
10         # 是因为在调用close()方法的时候,生成器不管你有没有捕获住异常,生成器就已经结束了.
11         # 在这里面捕获住但什么都没做,什么都没有做的情况之下会抛异常,如果将yield 2 和yield 3 语句注释掉,运行看一下.
12         pass
13 
14     yield 2
15     yield 3
16     return "jayden"
17 
18 
19 if __name__ == '__main__':
20     gen = gen_func()
21     print(next(gen))
22     # 捕获GeneratorExit后,在此处抛出异常.
23     gen.close()
24     next(gen)
25 
26 """
27 输出结果:
28     http://projectedu.com
29     Traceback (most recent call last):
30         File "F:/C/PythonProject/PythonHighLevelAndIo/test/chapter12/coroutine_gen_close_02.py", line 31, in <module>
31         gen.close()  --> 在这行语句抛出异常.
32     RuntimeError: generator ignored GeneratorExit 
33     生成器忽略生成器退出的异常 ,就是说在yield "http://projectedu.com"处捕获了GeneratorExit异常
34 """

 

coroutine_gen_close_03.py

 1 def gen_func():
 2     """
 3     生成器中只有一个yeild语句
 4     """
 5     # 把GeneratorExit异常捕获
 6     try:
 7         yield "http://projectedu.com"
 8     except GeneratorExit:
 9         # 在这里面捕获住但什么都没做,什么都没有做的情况之下会抛异常,如果将yield 2 和yield 3 语句注释掉,运行看一下.
10         # next(gen)这行语句抛异常,而gen.close()这行语句没有抛异常.
11         # 所以如果把GeneratorExit异常忽略掉,但是在下一步还有yield语句存在,这种情况之下,gen.close()语句会抛异常,
12         # 如果没有yield语句直接return,close()方法处不会抛异常.
13         pass
14 
15     # yield 2
16     # yield 3
17     return "jayden"
18 
19 
20 if __name__ == '__main__':
21     gen = gen_func()
22     print(next(gen))
23     # 捕获GeneratorExit后,在此处抛出异常.
24     gen.close()
25     next(gen)
26 
27 """
28 输出结果:
29 Traceback (most recent call last):
30   File "F:/C/PythonProject/PythonHighLevelAndIo/test/chapter12/coroutine_gen_close_03.py", line 33, in <module>
31     next(gen) --> 在这行语句抛异常,而gen.close()这行语句没有抛异常
32 StopIteration
33 http://projectedu.com
34 """

 

coroutine_gen_close_04.py

 1 def gen_func():
 2     """
 3     生成器中只有一个yeild语句
 4     """
 5     # 把GeneratorExit异常捕获
 6     try:
 7         yield "http://projectedu.com"
 8     except GeneratorExit:
 9         # 在这里面捕获住但什么都没做,什么都没有做的情况之下会抛异常,如果将yield 2 和yield 3 语句注释掉,运行看一下.
10         # next(gen)这行语句抛异常,而gen.close()这行语句没有抛异常.
11         # 所以如果把GeneratorExit异常忽略掉,但是在下一步还有yield语句存在,这种情况之下,gen.close()语句会抛异常,
12         # 如果没有yield语句直接return,close()方法处不会抛异常.
13         # 如果在捕获GeneratorExit异常里面执行操作,抛出StopIteration的话,调用close()函数处就不会抛异常,
14         # 而是在next(gen)语句处抛异常
15         # 说明:自己去处理GeneratorExit异常,最好在内部raise StopIteration.
16         # 如果不去对yield "http://projectedu.com"语句进行try...except...的话,gen.close()也是不会报错的,
17         # 只会在再次执行next(gen)语句处抛出异常StopIteration.说明生成器已经停止了.
18         # 如果对yield "http://projectedu.com"语句进行try...except...的话,并且在except中直接pass,忽略掉GeneratorExit异常.
19         # 错误异常是RuntimeError: generator ignored GeneratorExit,
20         # 这里面除了gen.close()是RuntimeError: generator ignored GeneratorExit异常之外,还需要注意,这个异常还会反应到
21         # 调用方里面来,就是调用close()函数处会抛异常,异常是向上抛的,抛到__main__里面来了.
22         # 如果在gen.close()下面再执行一行代码print("jayden"),是不会执行的.
23         raise StopIteration
24 
25     yield 2
26     yield 3
27     return "jayden"
28 
29 
30 if __name__ == '__main__':
31     gen = gen_func()
32     print(next(gen))
33     # 捕获GeneratorExit后,在此处抛出异常.
34     gen.close()
35     next(gen)
36 
37 """
38 输出结果:
39 Traceback (most recent call last):
40   File "F:/C/PythonProject/PythonHighLevelAndIo/test/chapter12/coroutine_gen_close_04.py", line 37, in <module>
41     next(gen)
42 StopIteration
43 http://projectedu.com
44 """

 

coroutine_gen_close_05.py

 1 def gen_func():
 2     """
 3     生成器中只有一个yeild语句
 4     """
 5     # 把GeneratorExit异常捕获
 6     try:
 7         yield "http://projectedu.com"
 8     except GeneratorExit:
 9         # 在这里面捕获住但什么都没做,什么都没有做的情况之下会抛异常,如果将yield 2 和yield 3 语句注释掉,运行看一下.
10         # next(gen)这行语句抛异常,而gen.close()这行语句没有抛异常.
11         # 所以如果把GeneratorExit异常忽略掉,但是在下一步还有yield语句存在,这种情况之下,gen.close()语句会抛异常,
12         # 如果没有yield语句直接return,close()方法处不会抛异常.
13         # 如果在捕获GeneratorExit异常里面执行操作,抛出StopIteration的话,调用close()函数处就不会抛异常,
14         # 而是在next(gen)语句处抛异常
15         # 说明:自己去处理GeneratorExit异常,最好在内部raise StopIteration.
16         # 如果不去对yield "http://projectedu.com"语句进行try...except...的话,gen.close()也是不会报错的,
17         # 只会在再次执行next(gen)语句处抛出异常StopIteration.说明生成器已经停止了.
18         # 如果对yield "http://projectedu.com"语句进行try...except...的话,并且在except中直接pass,忽略掉GeneratorExit异常.
19         # 错误异常是RuntimeError: generator ignored GeneratorExit,
20         # 这里面除了gen.close()是RuntimeError: generator ignored GeneratorExit异常之外,还需要注意,这个异常还会反应到
21         # 调用方里面来,就是调用close()函数处会抛异常,异常是向上抛的,抛到__main__里面来了.
22         # 如果在gen.close()下面再执行一行代码print("jayden"),是不会执行的.因为在gen.close()处抛出异常.
23         pass
24 
25     yield 2
26     yield 3
27     return "jayden"
28 
29 
30 if __name__ == '__main__':
31     gen = gen_func()
32     print(next(gen))
33     gen.close()
34     print("jayden")
35 
36 """
37 输出结果:
38 http://projectedu.com
39 Traceback (most recent call last):
40   File "F:/C/PythonProject/PythonHighLevelAndIo/test/chapter12/coroutine_gen_close_05.py", line 45, in <module>
41     gen.close()
42 RuntimeError: generator ignored GeneratorExit
43 """

coroutine_gen_close_06.py

 1 def gen_func():
 2     """
 3     生成器中只有一个yeild语句
 4     """
 5     # 如果我们不擅自处理GeneratorExit的话,就不会在gen.close()处抛异常
 6     # try:
 7     yield "http://projectedu.com"
 8     # except GeneratorExit:
 9     # pass
10 
11     yield 2
12     yield 3
13     return "jayden"
14 
15 
16 if __name__ == '__main__':
17     gen = gen_func()
18     print(next(gen))
19     gen.close()
20     print("jayden")
21 
22 """
23 输出结果:
24     http://projectedu.com
25     jayden
26     
27 综上:
28     coroutine_gen_close_03.py
29     coroutine_gen_close_04.py
30     coroutine_gen_close_05.py
31     coroutine_gen_close_06.py的代码
32     
33 我们自己不要随便去捕获异常.
34 """

 

 

 1 def gen_func():
 2     """
 3     生成器中只有一个yeild语句
 4     """
 5     try:
 6         yield "http://projectedu.com"
 7     except Exception:
 8         # 如果直接捕获Exception的话,gen.close()语句是不会报错的.
 9         # 在此处大家就会好奇了,很多人的理解就是Exception是最基础的Exception.其他的Exception都会继承Exception,
10         # 那实际上Exception能捕获到GeneratorExit这样的一个Exception,理论上也应该抛异常呀???
11         # 是因为GeneratorExit是继承自BaseException,并没有继承Exception.
12         # 用户在自定义Exception的时候,Exception才是我们的基类.
13         # BaseException比Exception是更基础的Exception.
14         # 如果使用BaseException来捕获GeneratorExit异常,
15         # 在gen.close()处就会抛出RuntimeError: generator ignored GeneratorExit异常.
16         pass
17 
18     yield 2
19     yield 3
20     return "jayden"
21 
22 
23 if __name__ == '__main__':
24     gen = gen_func()
25     print(next(gen))
26     gen.close()
27     print("jayden")
28 
29 """
30 输出结果:
31     http://projectedu.com
32     jayden
33 """

 

生成器throw()方法 

  • throw() 异常(传入一个异常)
  • throw()方法与异常处理有很大的关系.
 1 def gen_func():
 2     yield "http://projectedu.com"
 3     yield 2
 4     yield 3
 5     return "jayden"
 6 
 7 
 8 if __name__ == '__main__':
 9     gen = gen_func()
10     print(next(gen))
11     gen.throw(Exception, "download error")
12 
13 """
14 输出结果:
15 http://projectedu.com --> 可以打印出第一个yield的值.
16 Traceback (most recent call last):
17   File "F:/C/PythonProject/PythonHighLevelAndIo/test/chapter12/coroutine_gen_throw_01.py", line 24, in <module>
18     gen.throw(Exception, "download error")
19   File "F:/C/PythonProject/PythonHighLevelAndIo/test/chapter12/coroutine_gen_throw_01.py", line 15, in gen_func
20     
21     yield "http://projectedu.com" -->  
22     在throw的时候,异常是yield "http://projectedu.com"这行语句的异常,不再是向下执行yield 2这行的异常.
23     虽然把第一个yield的值输出出来了,但是在下面抛异常(gen.throw(Exception, "download error"))的时候,是前一个yield语句的异常.
24     throw()抛出异常的位置和close()抛出异常的位置是不一样的.
25     如果在执行close()方法的时候,
26         如果不去捕获第一个yield异常,第一个yield是不会出错的.
27         如果捕获异常但是没有去raise StopIteration的话,调用close()的地方就会出错.
28     执行throw()方法就恰恰相反
29         如果执行gen.throw()方法但是在yield语句中没有处理异常的话yield语句处会抛异常的.
30         所以说这种异常需要自己来处理.和close不一样.
31         
32 Exception: download error
33 """

 

 

 1 def gen_func():
 2     try:
 3         yield "http://projectedu.com"
 4     except Exception as e:
 5         pass
 6 
 7     yield 2
 8     yield 3
 9     return "jayden"
10 
11 
12 if __name__ == '__main__':
13     gen = gen_func()
14     print(next(gen))
15     gen.throw(Exception, "download error")
16 
17 """
18 输出结果:
19     http://projectedu.com
20 """

 

 

 1 def gen_func():
 2     try:
 3         yield "http://projectedu.com"
 4     except Exception as e:
 5         pass
 6 
 7     yield 2
 8     yield 3
 9     return "jayden"
10 
11 
12 if __name__ == '__main__':
13     gen = gen_func()
14     print(next(gen))
15     gen.throw(Exception, "download error")
16     # 再一次执行nexrt()方法
17     print(next(gen))
18 
19 
20 """
21 输出结果:
22     http://projectedu.com
23     3
24 """

 

 

 1 def gen_func():
 2     try:
 3         yield "http://projectedu.com"
 4     except Exception as e:
 5         pass
 6 
 7     yield 2
 8     yield 3
 9     return "jayden"
10 
11 
12 if __name__ == '__main__':
13     gen = gen_func()
14     print(next(gen))
15     gen.throw(Exception, "download error")
16     print(next(gen))
17     # 如果在这再次执行throw()方法.
18     gen.throw(Exception, "download error")
19 
20 """
21 输出结果:
22 Traceback (most recent call last):
23   File "F:/C/PythonProject/PythonHighLevelAndIo/test/chapter12/coroutine_gen_throw_04.py", line 31, in <module>
24     gen.throw(Exception, "download error")
25   File "F:/C/PythonProject/PythonHighLevelAndIo/test/chapter12/coroutine_gen_throw_04.py", line 21, in gen_func
26     yield 3 --> 抛出异常
27 http://projectedu.com --> 第一个yield的值输出
28 Exception: download error
29 3
30 
31 
32 通过生成器的三个方法得出,
33 通过close,send,throw实际上可以看到,对生成器的一个完善已经初步达到协程的要求,我们可以向生成器发送值,
34 可以暂停生成器,可以向生成器里面抛出异常,可以把生成器关闭掉.有了生成器的三个方法之后,
35 生成器基本上是可以完成我们前期对协程的一个功能需求.
36 
37 """

 

生成器实现协程

  • 引入协程的需求:
  1. 希望协程可以使用单线程来调度的,这样的话就不需要像线程一样让操作系统去调度.
  2. 协程是由程序员自己来进行调度的,协程没有深入到内核级别去进行调度.
  3. 线程和进程都是内核级别的程序调度.
  4. 协程是函数级别的一个程序.
  5. 协程是由程序员自己来决定什么时候来调用,并且同时能够像写同步代码一样来编写异步代码,就是说像写函数一样,
  6. 将一个函数变成协程
  7. 通过生成器的close(),throw()以及yield from这些功能就可以模拟协程的需求
  • 生成器是可以暂停的函数
  • 生成器是有状态的
 1 # 通过inspect包来获取生成器的状态.
 2 import inspect
 3 
 4 
 5 def gen_fun():
 6     yield 1
 7     return "python"
 8 
 9 
10 if __name__ == '__main__':
11     gen = gen_fun()
12 
13     # 获取生成器的状态
14     # GEN_CREATED 生成器创建状态
15     print(inspect.getgeneratorstate(gen))
16 
17     next(gen)
18     # GEN_SUSPENDED 生成器暂停状态
19     print(inspect.getgeneratorstate(gen))
20 
21     # 抛出异常StopIteration: python,因为没有yield语句,直接将返回值python放到异常的第一个参数中
22     # next(gen)
23     # print(inspect.getgeneratorstate(gen))
24 
25     try:
26         next(gen)
27     except StopIteration as e:
28         pass
29     # GEN_CLOSED 生成器关闭状态
30     print(inspect.getgeneratorstate(gen))
31 
32 """
33 生成器的很多功能就奠定了生成器去实现协程的一个基础.
34 在定义生成器的时候,生成器可以接收一个值 value = yield 1
35 
36 value = yield 1 有两个意思:
37     1.yield值给调用方
38     2.调用方通过send()方法的方式,发送值给生成器   
39 """

 

 

 1 def gen_fun():
 2     yield 1
 3     return "python"
 4 
 5 
 6 def gen_fun_temp():
 7     """
 8     value = yield 1 有两个意思:
 9         1.yield值给调用方
10         2.调用方通过send()方法的方式,发送值给生成器
11     """
12     # 有了这行语句,就可以称之为协程
13     value = yield 1
14     return "python"
15 
16 
17 def gen_fun_test():
18     # yield from可以将值yield的出去,并暂停生成器.
19     # yield from可以将子生成器委托给调用方,这样的话,当调用方向子生成器中传递一个异常的话,委托生成器就可以抛出异常.
20     value = yield from 1
21     return "python"
22 
23 # 试想如果有一个生成器,把这个生成器理解为协程,协程是用来下载网页的
24 
25 
26 """
27 因为协程与最开始只是yield 1方式的生成器有一个最大的区别就是协程可以消费从外边传递进来的数据,
28 也就说生成器有原来的一个生产者(之前只是yield值出去)变成了消费者,可以接收值进来,然后对接收值进行处理.
29 所以说协程与线程有很大相似的地方,就是可以在协程中加逻辑,外边传递值进来对传递值进行处理.
30 有value = yield 1 这种赋值语句,就可以称之为协程.
31 再加上yield from就更加奠定生成器可以实现协程的一个基础.
32 
33 1.希望用同步的方式编写异步的代码
34 2.在适当的时候暂停函数并在适当的时候启动函数
35 
36 """

 

 

  • 协程的调度依然是 事件循环 + 协程模式
  • 协程是单线程模式,整个线程都是在运行事件循环,直到某一个事件产生之后立马去调用协程里面的逻辑进行执行
  • 整个在编写协程的时候,遇到凡是耗费IO的操作,一定要去使用yield或者yield from这种模式,将它yield出去.
  • 一定不能再里面编写比如说time.sleep(10).在Tornado和Jevent都是不能这样写的,会以为他是一个单线程的模式.
  • 一定不能再协程的方法里面编写耗时的操作,对于耗时的操作直接将他yield出去.
  • 这是在理解事件循环+协程模式非常重要的点,而且是一个很大的坑.
 1 import socket
 2 from selectors import DefaultSelector, EVENT_READ, EVENT_WRITE
 3 
 4 # 实例化全局selector
 5 selector = DefaultSelector()
 6 
 7 
 8 def get_socket_data():
 9     yield "jayden"
10 
11 
12 def gen_sub_download_url(url):
13     """
14     下载url
15     gen_sub_download_url的好处:
16         生成器执行完1,2,3行代码后,直接将这些数据注册到selector中
17     """
18     # 不会耗IO的,因为非阻塞
19     client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
20     client.setblocking(False)
21     client.connect(("http://www.baidu.com", 80))
22 
23     # 将所有的数据注册到selector中,注册进去之后
24     selector.register(client.fileno(), EVENT_WRITE, connected)
25 
26     # 这个协程在什么时候调用,是在selector selected调用之后就是某一个socket可读的时候.会读出数据.
27     # 在这里模拟的是yield "jayden",实际上是在socket中读取数据的,yield from将数据放到source里面之后
28     # 这个地方会暂停,什么时候被调用起来呢???就是get_socket_data这里面如果运行一个事件循环,会去监听socket,
29     # socket会去运行get_socket_data协程,get_socket_data协程运行之后,去唤醒gen_download_html,唤醒后将
30     # yield "jayden" 的值放到source中后,而且向下执行,将html文件重新组织一下变成真正的html.
31     source = yield from get_socket_data()
32 
33     data = source.decode("utf8")
34     html_data = source.split("\r\n\r\n")[1]
35     print(html_data)
36 
37 
38 def gen_download_html(html):
39     # 协程会遇到yield或yield from会暂停,将操控权交出,这个地方模拟网络IO的请求
40     html = yield from gen_sub_download_url()

 

 

 

 

 

 

 

 

 

 

 

 

 

 

********

posted on 2019-05-11 10:58  jaydenjune  阅读(48)  评论(0)    收藏  举报

导航