Python Day10

一、协程

1.协程介绍

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

协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:

协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。线程和进程的操作是由程序触发系统接口,最后的执行者是系统;协程的操作执行者则是用户自身程序。

 

简单定义:

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

协程的优点:

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

 

缺点:

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

协程的适用场景:当程序中存在大量不需要CPU的操作时(也就是平时所说的IO密集型程序),适用于协程。

yield实现的简单协程:

 1 import time
 2
 3 def consumer(name):
 4     print("--->starting eating baozi...")
 5     while True:
 6         new_baozi = yield
 7         print("[%s] is eating baozi %s" % (name, new_baozi))
 8         time.sleep(1)
 9
10 def producer():
11     r = con.__next__()
12     r = con2.__next__()
13     n = 0
14     while n < 5:
15         n += 1
16         con.send(n)
17         con2.send(n)
18         print("\033[32;1m[producer]\033[0m is making baozi %s" % n)
19
20 if __name__ == ‘__main__‘:
21     con = consumer("c1")
22     con2 = consumer("c2")
23     producer()

2.Greenlet

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

 1 from greenlet import greenlet
 2
 3 def test1():
 4     print(12)
 5     gr2.switch()
 6     print(34)
 7     gr2.switch()
 8
 9 def test2():
10     print(56)
11     gr1.switch()
12     print(78)
13
14 gr1 = greenlet(test1)
15 gr2 = greenlet(test2)
16 gr1.switch()

3.Gevent

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

 1 import gevent
 2
 3 def func1():
 4     print(‘\033[31;1ma在跟b搞...\033[0m‘)
 5     gevent.sleep(2)
 6     print(‘\033[31;1ma又回去跟继续跟b搞...\033[0m‘)
 7
 8 def func2():
 9     print(‘\033[32;1ma切换到了跟c搞...\033[0m‘)
10     gevent.sleep(1)
11     print(‘\033[32;1ma搞完了d两次,回来继续跟c搞...\033[0m‘)
12
13 def func3():
14     print(‘\033[32;1ma切换到了跟d搞...\033[0m‘)
15     gevent.sleep(0)
16     print(‘\033[32;1ma搞完了d,觉得不爽,又搞了d一次...\033[0m‘)
17
18 gevent.joinall([
19     gevent.spawn(func1),
20     gevent.spawn(func2),
21     gevent.spawn(func3),
22 ])

输出结果:

同步与异步的性能区别

 1 import gevent
 2
 3 def task(pid):
 4     """
 5     Some non-deterministic task
 6     """
 7     gevent.sleep(0.5)
 8     print(‘Task %s done‘ % pid)
 9
10 def synchronous():
11     for i in range(1, 10):
12         task(i)
13
14 def asynchronous():
15     threads = [gevent.spawn(task, i) for i in range(10)]
16     gevent.joinall(threads)
17
18 print(‘Synchronous:‘)
19 synchronous()
20
21 print(‘Asynchronous:‘)
22 asynchronous()

上面程序的重要部分是将task函数封装到Greenlet内部线程的gevent.spawn。 初始化的greenlet列表存放在数组threads中,此数组被传给gevent.joinall 函数,后者阻塞当前流程,并执行所有给定的greenlet。执行流程只会在 所有greenlet执行完后才会继续向下走。

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

 1 from urllib import request
 2 import gevent
 3 import time
 4 from gevent import monkey
 5 monkey.patch_all()  # 把当前程序的所有的io操作给我单独的做上标记
 6
 7 def f(url):
 8     print(‘GET: %s‘ % url)
 9     resp = request.urlopen(url)
10     data = resp.read()
11     print(‘%d bytes received from %s.‘ % (len(data), url))
12
13 urls = [‘https://www.python.org/‘,
14         ‘https://www.yahoo.com/‘,
15         ‘https://github.com/‘]
16
17 time_start = time.time()
18 for url in urls:
19     f(url)
20 print("同步cost", time.time() - time_start)
21
22 async_time_start = time.time()
23 gevent.joinall([
24     gevent.spawn(f, ‘https://www.python.org/‘),
25     gevent.spawn(f, ‘https://www.yahoo.com/‘),
26     gevent.spawn(f, ‘https://github.com/‘),
27 ])
28 print("异步cost", time.time() - async_time_start)

输出结果:

通过gevent实现单线程下的多socket并发

 1 import sys
 2 import socket
 3 import time
 4 import gevent
 5
 6 from gevent import socket,monkey
 7 monkey.patch_all()
 8
 9
10 def server(port):
11     s = socket.socket()
12     s.bind((‘0.0.0.0‘, port))
13     s.listen(500)
14     while True:
15         cli, addr = s.accept()
16         gevent.spawn(handle_request, cli)
17
18
19
20 def handle_request(conn):
21     try:
22         while True:
23             data = conn.recv(1024)
24             print("recv:", data)
25             conn.send(data)
26             if not data:
27                 conn.shutdown(socket.SHUT_WR)
28
29     except Exception as  ex:
30         print(ex)
31     finally:
32         conn.close()
33 if __name__ == ‘__main__‘:
34     server(8001)

 


二、事件驱动

通常,我们写服务器处理模型的程序时,有以下几种模型:

(1)每收到一个请求,创建一个新的进程,来处理该请求;

(2)每收到一个请求,创建一个新的线程,来处理该请求;

(3)每收到一个请求,放入一个事件列表,让主进程通过非阻塞I/O方式来处理请求

上面的几种方式,各有千秋,

第(1)中方法,由于创建新的进程的开销比较大,所以,会导致服务器性能比较差,但实现比较简单。

第(2)种方式,由于要涉及到线程的同步,有可能会面临死锁等问题。

第(3)种方式,在写应用程序代码时,逻辑比前面两种都复杂。

综合考虑各方面因素,一般普遍认为第(3)种方式是大多数网络服务器采用的方式

看图说话讲事件驱动模型

在UI编程中,常常要对鼠标点击进行相应,首先如何获得鼠标点击呢?

方式一:创建一个线程,该线程一直循环检测是否有鼠标点击,那么这个方式有以下几个缺点:

1. CPU资源浪费,可能鼠标点击的频率非常小,但是扫描线程还是会一直循环检测,这会造成很多的CPU资源浪费;如果扫描鼠标点击的接口是阻塞的呢?

2. 如果是堵塞的,又会出现下面这样的问题,如果我们不但要扫描鼠标点击,还要扫描键盘是否按下,由于扫描鼠标时被堵塞了,那么可能永远不会去扫描键盘;

3. 如果一个循环需要扫描的设备非常多,这又会引来响应时间的问题;

所以,该方式是非常不好的。

方式二:就是事件驱动模型

目前大部分的UI编程都是事件驱动模型,如很多UI平台都会提供onClick()事件,这个事件就代表鼠标按下事件。事件驱动模型大体思路如下:

1. 有一个事件(消息)队列;

2. 鼠标按下时,往这个队列中增加一个点击事件(消息);

3. 有个循环,不断从队列取出事件,根据不同的事件,调用不同的函数,如onClick()、onKeyDown()等;

4. 事件(消息)一般都各自保存各自的处理函数指针,这样,每个消息都有独立的处理函数;

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

让我们用例子来比较和对比一下单线程、多线程以及事件驱动编程模型。下图展示了随着时间的推移,这三种模式下程序所做的工作。这个程序有3个任务需要完成,每个任务都在等待I/O操作时阻塞自身。阻塞在I/O操作上所花费的时间已经用灰色框标示出来了。

在单线程同步模型中,任务按照顺序执行。如果某个任务因为I/O而阻塞,其他所有的任务都必须等待,直到它完成之后它们才能依次执行。这种明确的执行顺序和串行化处理的行为是很容易推断得出的。如果任务之间并没有互相依赖的关系,但仍然需要互相等待的话这就使得程序不必要的降低了运行速度。

在多线程版本中,这3个任务分别在独立的线程中执行。这些线程由操作系统来管理,在多处理器系统上可以并行处理,或者在单处理器系统上交错执行。这使得当某个线程阻塞在某个资源的同时其他线程得以继续执行。与完成类似功能的同步程序相比,这种方式更有效率,但程序员必须写代码来保护共享资源,防止其被多个线程同时访问。多线程程序更加难以推断,因为这类程序不得不通过线程同步机制如锁、可重入函数、线程局部存储或者其他机制来处理线程安全问题,如果实现不当就会导致出现微妙且令人痛不欲生的bug。

在事件驱动版本的程序中,3个任务交错执行,但仍然在一个单独的线程控制中。当处理I/O或者其他昂贵的操作时,注册一个回调到事件循环中,然后当I/O操作完成时继续执行。回调描述了该如何处理某个事件。事件循环轮询所有的事件,当事件到来时将它们分配给等待处理事件的回调函数。这种方式让程序尽可能的得以执行而不需要用到额外的线程。事件驱动型程序比多线程程序更容易推断出行为,因为程序员不需要关心线程安全问题。

当我们面对如下的环境时,事件驱动模型通常是一个好的选择:

  • 程序中有许多任务,而且…
  • 任务之间高度独立(因此它们不需要互相通信,或者等待彼此)而且…
  • 在等待事件到来时,某些任务会阻塞。

当应用程序需要在任务间共享可变的数据时,这也是一个不错的选择,因为这里不需要采用同步处理。

网络应用程序通常都有上述这些特点,这使得它们能够很好的契合事件驱动编程模型。

此处要提出一个问题,就是,上面的事件驱动模型中,只要一遇到IO就注册一个事件,然后主程序就可以继续干其它的事情了,只到io处理完毕后,继续恢复之前中断的任务,这本质上是怎么实现的呢?稍后揭晓。

时间: 2024-10-27 06:01:04

Python Day10的相关文章

Python Day10(补充)

一.阻塞IO.非阻塞IO.IO多路复用.信号驱动IO.异步IO 同步IO和异步IO,阻塞IO和非阻塞IO分别是什么,到底有什么区别?不同的人在不同的上下文下给出的答案是不同的.所以先限定一下本文的上下文. 本文讨论的背景是Linux环境下的network IO.     1.概念说明 在进行解释之前,首先要说明几个概念: 用户空间和内核空间 进程切换 进程的阻塞 文件描述符 缓存 I/O 用户空间与内核空间 现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为

学习python:day10

一. 进程 1. 多进程multiprocessing multiprocessing包是Python中的多进程管理包,是一个跨平台版本的多进程模块.与threading.Thread类似,它可以利用multiprocessing.Process对象来创建一个进程.该进程可以运行在Python程序内部编写的函数.该Process对象与Thread对象的用法类似. 创建一个Process实例,可用start()方法启动. join()方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步.

老男孩python DAY10 soket 编程

socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,应用程序通常通过"套接字"向网络发出请求或者应答网络请求. socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,对于文件用[打开][读写][关闭]模式来操作.socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO.打开.关闭) socket和file的区别: file模块是针对某个指定文件进行[打开

Python Day10 MySQL 01

MySQL数据库基本管理 Window版本安装 1.下载:MySQL Community Server 5.7.16http://dev.mysql.com/downloads/mysql/ 2.解压如果想要让MySQL安装在指定目录,那么就将解压后的文件夹移动到指定目录,如:C:\mysql-5.7.16-winx64 3.添加环境变量[右键计算机]-->[属性]-->[高级系统设置]-->[高级]-->[环境变量]-->[在第二个内容框中找到 变量名为Path 的一行,双

python对象反射和函数反射

[原文] python的对象反射功能,经常在编程时使用.相比较其它的编程语言使用非常方便.反射就是用字符串来操作对象或者类,模块中的成员. 一.对象的反射 反射功能的实现,由这4个内置函数来实现(hasattr, getattr, setattr, delattr) 1.1.hasattr判断是否有某个成员 判断对象中是否有属性, 方法.返回bool值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #!/usr/bin/env python

python自动化运维之路~DAY10

python自动化运维之路~DAY10 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任.

Python 元组day10

Python的元组与列表类似,不同之处在于元组的元素不能修改. 元组使用小括号,列表使用方括号. 元组创建很简单,只需要在括号中添加元素,并使用逗号隔开即可. 如下实例: tup1 = ('physics', 'chemistry', 1997, 2000);tup2 = (1, 2, 3, 4, 5 );tup3 = "a", "b", "c", "d"; 创建空元组 tup1 = (); 元组中只包含一个元素时,需要在元素

Python小白-day10 memcache&redis

Memcached Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载.它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态.数据库驱动网站的速度.Memcached基于一个存储键/值对的hashmap.其守护进程(daemon )是用C写的,但是客户端可以用任何语言来编写,并通过memcached协议与守护进程通信. Python操作Memcached 1.第一次操作 import memcache   mc = memcache.Clie

Python线程和协程-day10

写在前面 上课第10天,打卡: 感谢Egon老师细致入微的讲解,的确有学到东西! 一.线程 1.关于线程的补充 线程:就是一条流水线的执行过程,一条流水线必须属于一个车间: 那这个车间的运行过程就是一个进程: 即一个进程内,至少有一个线程: 进程是一个资源单位,真正干活的是进程里面的线程: 线程是一个执行单位: 多线程:一个车间内有多条流水线,多个流水线共享该车间的资源: 一个进程内有多个线程,多线程共享一个进程的资源: 线程创建的开销要远远小于创建进程的开销: 进程之间更多的是一种竞争关系: