协程(08-18)

一. 协程

1. 协程:又称微线程,是一种用户态的轻量级线程。

             协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态,换种说法,进入上一次离开时所处逻辑流的位置。

2. 优点:

          无需线程上下文切换的开销;

          无需原子操作锁定及同步的开销;

          方便切换控制流,简化编程模型;

         高并发+高扩展性,低成本:一个CPU支持上万个协程都不是问题,很适合高并发处理。 

3. 缺点:

           无法利用多核资源,协程本质上是个单线程,协程需要和进程配合才能运行在多CPU上;

           进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序。

4. Greenlet

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

                .switch  #还是手动切换;到Gevent就是自动切换了。

5. Gevent    (自动切换)

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

     

 #import gevent

 1 import gevent
 2 
 3 
 4 def foo():
 5     print("Running in foo")
 6     gevent.sleep(2)
 7     print("Explicit context switch to foo again")
 8 def bar():
 9     print("Explicit context switch to bar")
10     gevent.sleep(1)
11     print("Implicit context switch back to bar")
12 def fun3():
13     print("running fun3")
14     gevent.sleep(0)
15     print("running fun3 again")
16 
17 gevent.joinall([
18     gevent.spawn(foo),  #生成
19     gevent.spawn(bar),
20     gevent.spawn(fun3),
21 ])

运行结果:

Running in foo
Explicit context switch to bar
running fun3
running fun3 again
Implicit context switch back to bar
Explicit context switch to foo again

#程序运行2秒结束。

6. 协程之爬虫

   爬取简单的网页,并保存下来。

 1 import urllib
 2 from urllib import request
 3 
 4 def f(url):
 5     print("GET: %s" % url)
 6     resp = urllib.request.urlopen(url)
 7     data = resp.read()
 8     f = open("url.html","wb")
 9     f.write(data)
10     f.close()
11     print('%d bytes received from %s.' % (len(data), url))
12 
13 f("http://www.cnblogs.com/alex3714/articles/5248247.html")

这只是简单的爬取一个网页,大数据时代下,如何爬取大范围的数据——并发的爬取:多线程,多进程,协程;

如下:

 1 import urllib
 2 from urllib import request
 3 import gevent
 4 from gevent import monkey
 5 
 6 monkey.patch_all()  #把当前程序的所有io操作单独的做上标记(补丁)  标记出来是Io操作,实现自动切换
 7 def f(url):
 8     print("GET: %s" % url)
 9     resp = urllib.request.urlopen(url)
10     data = resp.read()
11     f = open("url.html","wb")
12     f.write(data)
13     f.close()
14     print('%d bytes received from %s.' % (len(data), url))
15 
16 
17 gevent.joinall([
18     gevent.spawn(f,"http://www.cnblogs.com/alex3714/articles/5248247.html"),  #生成
19     gevent.spawn(f,"https://mbd.baidu.com/newspage/data/landingsuper?context=%7B%22nid%22%3A%22news_8563932941311660974%22%7D&n_type=0&p_from=1"),
20     gevent.spawn(f,"https://mbd.baidu.com/newspage/data/landingsuper?context=%7B%22nid%22%3A%22news_9236232113644194162%22%7D&n_type=0&p_from=1"),
21 ])

       

 7. 协程之Socket

 

遇到IO实现自动切换,那什么时候,怎么再切换回来????  回调函数(事件驱动方式)

8.论事件驱动与异步IO

 8.1事件驱动模型

目前大部分的UI编程都是事件驱动模型,如很多UI(UI即User Interface(用户界面))平台都会提供onClick()事件,这个事件就代表鼠标按下事件。

事件驱动模型大体思路如下:

1. 有一个事件(消息)队列;
2. 鼠标按下时,往这个队列中增加一个点击事件(消息);
3. 有个循环,不断从队列取出事件,根据不同的事件,调用不同的函数,如onClick()、onKeyDown()等;
4. 事件(消息)一般都各自保存各自的处理函数指针,这样,每个消息都有独立的处理函数;

事件驱动编程是一种(网络)编程范式,这里程序的执行流由外部事件来决定。它的特点是包含一个事件循环,当外部事件发生时使用回调机制来触发相应的处理。另外两种常见的编程范式是(单线程)同步以及多线程编程

9.  IO多路复用

参考老男孩博客:http://www.cnblogs.com/alex3714/articles/5876749.html 

9.1基本概念

- 用户空间和内核空间

- 进程切换

- 进程的阻塞

- 文件描述符

- 缓存 I/O

用户空间与内核空间

现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方)。操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操心系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。

进程的阻塞

正在执行的进程,由于期待的某些事件未发生,如请求系统资源失败、等待某种操作的完成、新数据尚未到达或无新工作做等,则由系统自动执行阻塞原语(Block),使自己由运行状态变为阻塞状态。可见,进程的阻塞是进程自身的一种主动行为,也因此只有处于运行态的进程(获得CPU),才可能将其转为阻塞状态。当进程进入阻塞状态,是不占用CPU资源的

文件描述符fd

文件描述符(File descriptor)是计算机科学中的一个术语,是一个用于表述指向文件的引用的抽象化概念。

文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。

缓存 I/O

缓存 I/O 又被称作标准 I/O,大多数文件系统的默认 I/O 操作都是缓存 I/O。在 Linux 的缓存 I/O 机制中,操作系统会将 I/O 的数据缓存在文件系统的页缓存( page cache )中,也就是说,数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间

缓存 I/O 的缺点:
数据在传输过程中需要在应用程序地址空间和内核进行多次数据拷贝操作,这些数据拷贝操作所带来的 CPU 以及内存开销是非常大的。

9.2   IO模式

内核态到用户态的数据拷贝

 ①   linux系统的五种网络模式的方案。
- 阻塞 I/O(blocking IO)


- 非阻塞 I/O(nonblocking IO)

                     nonblocking IO的特点是用户进程需要不断的主动询问kernel数据好了没有。


- I/O 多路复用( IO multiplexing)

                     IO multiplexing就是我们说的select,poll,epoll,有些地方也称这种IO方式为event driven IO(事件驱动IO)。select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。它的基本原理就是select,poll,epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。


- 信号驱动 I/O( signal driven IO)


- 异步 I/O(asynchronous IO)

 

10.  Select 解析Socket    (基本不用)

10.1   sellect、poll、epoll三者的区别

select(支持windows )

select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点,事实上从现在看来,这也是它所剩不多的优点之一。

select的一个缺点在于单个进程能够监视的文件描述符的数量存在最大限制;

调用select()会对所有socket进行一次线性扫描,所以这也浪费了一定的开销。

应用:游戏开发

poll

它和select在本质上没有多大差别,但是poll没有最大文件描述符数量的限制;

poll和select同样存在一个缺点就是:会对所有socket进行一次线性扫描,所以这也浪费了一定的开销。

epoll(不支持Windows)

优点:poll没有最大文件描述符数量的限制

  说明:本人博客是参考优秀博客学习,仅是自己学习的一个记录,

来源:老男孩博客:http://www.cnblogs.com/alex3714/articles/5248247.html     

原文地址:https://www.cnblogs.com/bltstop/p/10127390.html