python中协程

在引出协成概念之前先说说python的进程和线程。

进程:

进程是正在执行程序实例。执行程序的过程中,内核会讲程序代码载入虚拟内存,为程序变量分配空间,建立 bookkeeping 数据结构,来记录与进程有关的信息,

比如进程 ID,用户 ID 等。在创建进程的时候,内核会为进程分配一定的资源,并在进程存活的时候不断进行调整,比如内存,进程创建的时候会占有一部分内存。

进程结束的时候资源会释放出来,来让其他资源使用。我们可以把进程理解为一种容器,容器内的资源可多可少,但是在容器内的程序只能使用容器内的东西。因此启动

进程的时候会比较慢,尤其是windows,尤其是多进程的时候(最好是在密集性运算的时候启动多进程)

线程:

  一个进程中可以执行多个线程。多个线程共享进程内的资源。所以可以将线程可以看成是共享同一虚拟内存以及其他属性的进程。
线程相对于进程的优势在于同一进程下的不同线程之间的数据共享更加容易。

在说到线程的时候说说GIL(全局解释性锁 GLOBAL INTERPRETER LOCK),GIL 的存在是为了实现 Python 中对于共享资源访问的互斥。而且是非常霸道的解释器级别的互斥。在 GIL 的机制下,一个线程访问解释器之后,其他的线程就需要等待这个线程释放之后才可以访问。这种处理方法在单处理器下面并没有什么问题,单处理器的本质是串行执行的。但是再多处理器下面,这种方法会导致无法利用多核的优势。Python 的线程调度跟操作系统的进程调度类似,都属于抢占式的调度。一个进程执行了一定时间之后,发出一个信号,操作系统响应这个时钟中断(信号),开始进程调度。而在 Python 中,则通过软件模拟这种中断,来实现线程调度。比如:对全局的num做加到100的操作,可能在你加到11的时候,还没加完,则CPU就交给另一个线程处理,所以最后的结果可能比100会小或者比100会大。

简单的说说进程和线程的几点关系

1、启动一个进程至少会有一个线程

2、修改主线程的数据会影响到子线程的数据,因为他们之间内存是共享的,修改主进程不会影响到子进程的数据,两个子进程之间是相互独立的,如果要实现子进程间的通信,可以利用中间件,比如multiprocessing的Queue。

如:

 1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3
4 #进程之间的通信
5 from multiprocessing import Process,Queue
6
7 def f(qq):
8 #在子进程设置值,本质上是子进程pickle数据序列化到公共的地方
9 qq.put(['hello',None,123])
10
11
12 if __name__ == '__main__':
13 q = Queue()
14 t = Process(target=f,args=(q,))
15 t.start()
16 #从父进程中取出来,本质上是父进程pickle从公共的地方把数据反序列化出来
17 print q.get()
18 t.join()

3、新的线程很容易被创建,但是新的进程需要对其父进程进行一次克隆

4、一个线程可以操作和控制同一个进程里的其他线程,但进程只能操作其子进程。

明白了进程和线程的概念之后,说说协成。

协程:

  协程,又称微线程。英文名Coroutine。

协程最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。

第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

因为协程是一个线程执行,那怎么利用多核CPU呢?最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。

用yield来实现传统的生产者-消费者模型是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待。

 1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3
4 import time
5
6 def consumer():
7 r = ''
8 while True:
9 n = yield r
10 if not n:
11 return
12 print('Consume running %s...' % n)
13 time.sleep(1) #遇到阻塞到produce执行
14 r = '200 OK'
15
16 def produce(c):
17 c.next() #启动迭代器
18 n = 0
19 while n < 5:
20 n = n + 1
21 print('[Produce] running %s...' % n)
22 r = c.send(n) #到consumer中执行
23 print('[Consumer] return: %s' % r)
24 c.close()
25
26 if __name__=='__main__':
27 c = consumer() #迭代器
28 produce(c)
29
30 执行结果:
31 [Produce] running 1...
32 Consume running 1...
33 [Consumer] return: 200 OK
34 [Produce] running 2...
35 Consume running 2...
36 [Consumer] return: 200 OK
37 [Produce] running 3...
38 Consume running 3...
39 [Consumer] return: 200 OK
40 [Produce] running 4...
41 Consume running 4...
42 [Consumer] return: 200 OK
43 [Produce] running 5...
44 Consume running 5...
45 [Consumer] return: 200 OK

其实python有个模块封装了协程功能,greenlet.来看代码。

 1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3
4 #封装好的协成
5 from greenlet import greenlet
6
7 def test1():
8 print "test1:",11
9 gr2.switch()
10 print "test1:",12
11 gr2.switch()
12
13 def test2():
14 print "test2:",13
15 gr1.switch()
16 print "test2:",14
17
18
19 gr1 = greenlet(test1)
20 gr2 = greenlet(test2)
21 gr1.switch()
22
23 执行结果:
24 test1: 11
25 test2: 13
26 test1: 12
27 test2: 14

这个还的人工切换,是不是觉得太麻烦了,不要捉急,python还有一个自动切换比greenlet更强大的gevent。

其原理是当一个greenlet遇到IO操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。

由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO。直接上代码

 1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3
4 #协成的自动切换
5 import gevent
6 import time
7
8 def func1():
9 print('\033[31;1m 正在执行 111...\033[0m')
10 gevent.sleep(2)
11 print('\033[31;1m 正在执行 444...\033[0m')
12
13
14 def func2():
15 print('\033[32;1m 正在执行 222...\033[0m')
16 gevent.sleep(3) #阻塞3秒,所以自动切换到func1,执行完func1后 再切换回来
17 print('\033[32;1m 正在执行 333...\033[0m')
18
19 start_time = time.time()
20 gevent.joinall([
21 gevent.spawn(func1),
22 gevent.spawn(func2),
23 # gevent.spawn(func3),
24 ])
25 end_time = time.time()
26
27 #程序总共花费3秒执行
28 print "spend",(end_time-start_time),"second"
29
30 执行结果:
31 正在执行 111...
32 正在执行 222...
33 正在执行 444...
34 正在执行 333...
35 总耗时:
36 spend 3.00936698914 second

下面我们用greenlet来实现一个socket多线程处理数据的功能。不过需要安装一个monkey补丁,请自行安装吧。

client端:

 1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3
4
5 from socket import *
6
7 ADDR, PORT = 'localhost', 8001
8 client = socket(AF_INET,SOCK_STREAM)
9 client.connect((ADDR, PORT))
10
11 while 1:
12 cmd = raw_input('>>:').strip()
13 if len(cmd) == 0: continue
14 client.send(cmd)
15 data = client.recv(1024)
16 print data
17 #print('Received', repr(data))
18
19 client.close()

server端:

 1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3
4
5 import sys
6 import socket
7 import gevent
8 from gevent import monkey
9 monkey.patch_all()
10
11 def server(port):
12 sock = socket.socket()
13 sock.bind(('127.0.0.1', port))
14 sock.listen(500)
15 while 1:
16 conn, addr = sock.accept()
17 #handle_request(conn)
18 gevent.spawn(handle_request, conn)
19
20
21 def handle_request(conn):
22 try:
23 while 1:
24 data = conn.recv(1024)
25 if not data:
26 break
27 print("recv:",data)
28 conn.send(data)
29
30 except Exception as ex:
31 print(ex)
32 finally:
33 conn.close()
34
35 if __name__ == '__main__':
36 server(8001)

以上代码可以自行多开几个客户端,然后执行看看,是不是很酷,无论客户端输入什么,服务端都能实时接收到。

OVER!

 
分类: python

协程python的更多相关文章

  1. Python—进程、线程、协程

    一.线程 线程是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位.一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务 方法: ...

  2. Python之路【第七篇】:线程、进程和协程

    Python之路[第七篇]:线程.进程和协程   Python线程 Threading用于提供线程相关的操作,线程是应用程序中工作的最小单元. 1 2 3 4 5 6 7 8 9 10 11 12 1 ...

  3. python中的协程及实现

    1.协程的概念: 协程是一种用户态的轻量级线程.协程拥有自己的寄存器上下文和栈. 协程调度切换时,将寄存器上下文和栈保存到其他地方,在切换回来的时候,恢复先前保存的寄存器上下文和栈. 因此,协程能保留 ...

  4. 从yield 到yield from再到python协程

    yield 关键字 def fib(): a, b = 0, 1 while 1: yield b a, b = b, a+b yield 是在:PEP 255 -- Simple Generator ...

  5. Python 实现协程

    协程的概念 协程,又称微线程,纤程.英文名Coroutine.一句话说明什么是线程:协程是一种用户态的轻量级线程.(其实并没有说明白~) 我觉得单说协程,比较抽象,如果对线程有一定了解的话,应该就比较 ...

  6. python入门20180717-迭代器、生成器和协程

    迭代器.生成器和协程 python中任意的对象,只要它定义了可以返回一个迭代器的__iter__方法,或者支持下标索引的_getitem_方法,那么它就是一个可迭代对象. 可迭代的对象不一定就是迭代器 ...

  7. Python进程、线程、协程及IO多路复用

    详情戳击下方链接 Python之进程.线程.协程 python之IO多路复用

  8. Python与Golang协程异同

    背景知识 这里先给出一些常用的知识点简要说明,以便理解后面的文章内容. 进程的定义: 进程,是计算机中已运行程序的实体.程序本身只是指令.数据及其组织形式的描述,进程才是程序的真正运行实例. 线程的定 ...

  9. 这篇文章揭开python进程、线程、协程神秘的面纱

    1.概念 [关注公众号"轻松学编程"了解更多. 回复"协程"获取本文源代码.] 从计算机硬件角度: 计算机的核心是CPU,承担了所有的计算任务. 一个CPU,在 ...

随机推荐

  1. Socket的简单使用

    一.Socket: Socket又称”套接字" 网络上的两个程序通过一个双向的通信链接实现数据的交换,这个连接的一端成为一个socket 应用程序通常通过”套接字”向网络发出请求或者应答网络 ...

  2. “vmware tools 只能虚拟机中安装”的解决方法

    vmware安装的一个大坑,最近在开发上需要用到centos 6.4,由于我本身的系统是win8所以决定使用虚拟机,选择了vmware,并且从网上下载的虚拟机的映像文件.中间安装了vmware8,安装 ...

  3. JAVA 8 Optional类介绍及其源码

    什么是Optional对象 Java 8中所谓的Optional对象,即一个容器对象,该对象可以包含一个null或非null值.如果该值不为null,则调用isPresent()方法将返回true,且 ...

  4. CMPP3.0 长短信实现方案

    长短信息:是指超过70个汉字,140个字节的信息内容 一.CMPP协议相关字段分析 CMPP协议具体部分请参考<中国移动互联网短信网关接口协议(V3.0.0).doc> CMPP_SUBM ...

  5. 0001 Oracle数据库安装

    从这个月初开始学习Oracle,因为完全是零起步,就从Oracle的下载安装开始一点一点学起,今天把系统重新做了,就再安装了一遍Oracle11gR2,把安装过程记录一下: 一.安装Oracle数据库 ...

  6. 一个update的小故事

    偶尔测试了一段小代码,写个循环 if object_id('tempdb..#TB') is not null drop table #TB go create table #TB ( ID int ...

  7. Windows环境下载与安装JBOSS服务器的详细图文教程

    一.JDK的安装 首先安装JDK,配置环境变量(PATH,CLASSPATH,JAVA_HOME). 可以参照:Windows环境下JDK安装与环境变量配置 二.Jboss的介绍 JBOSS是EJB的 ...

  8. JVM探索之内存管理(三)

    上节我们介绍了JVM垃圾回收的原则,还有几个垃圾收集算法:标记-清除算法.复制算法.标记整理算法.分代收集算法:现在将要说HotSpt的垃圾收集器,这小节将只是理论. Java虚拟机规范对垃圾收集器的 ...

  9. Redisd VS Memcached

    Redis也常常被当作 Memcached的挑战者被提到桌面上来.关于Redis与Memcached的比较更是比比皆是.然而,Redis真的在功能.性能以及内存使用效率上都超越了Memcached吗? ...

  10. 错误 未能找到类型或命名空间名称 (是否缺少 using 指令或程序集引用?)

    有时发现,明明引用了,结果却提示未引用, 这时就有可能是两个程序集的目标框架类型不一致导致的(在程序集属性面板里改下即可).