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. vs2015密钥 企业版 专业版 (vs.net)

    专业版:HMGNV-WCYXV-X7G9W-YCX63-B98R2企业版:HM6NR-QXX7C-DFW2Y-8B82K-WTYJV

  2. 玩了一天的Git

    今天的逗比事 Git从学习到使用,也有2个月时间了,一开始也就看看廖雪峰老师的Git教程,跟着做了一遍,感觉非常受用. 遇到一些忘掉的命令,再回去查查也基本都没问题. 但是今天缺遇到了逗比事,回过头来 ...

  3. yii2 如何在页面底部加载css和js

    作者:白狼 出处:www.manks.top/article/yii2_load_js_css_in_end 本文版权归作者,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接 ...

  4. 关于tempdb的一些注意事项

    由于数据库的文件的位置对于I/O性能如此重要,以至于在创建主数据文件的文职时,需要考虑tempdb性能对系统性的影响,因为它是最动态的数据库,速度还需要最快. 组成:有主数据文件和日志文件组成.从sq ...

  5. SQLServer基本函数

    1.字符串函数  长度与分析用 datalength(Char_expr) 返回字符串包含字符数,但不包含后面的空格 substring(expression,start,length) 取子串 ri ...

  6. C+命令行+方向键=简易版扫雷

    前言: 想起来做这个是因为那时候某天知道了原来黑框框里面的光标是可以控制的,而且又经常听人说起这个,就锻炼一下好了. 之前就完成了那1.0的版本,现在想放上来分享却发现有蛮多问题的,而且最重要的是没什 ...

  7. Linux计时器

    Linux中, 系统为每个系统都维护了三种计时器,分别为: 真实计数器, 虚拟计时器以及实用计时器, 一般情况下都使用真实计时器 getitimer()/setitimer() //读取/设置内部计时 ...

  8. Android UI编程(1)——九宫格(GridView)

    (转自:http://blog.csdn.net/Thanksgining/article/details/42968847) 参考博客:http://blog.csdn.net/xyz_lmn/ar ...

  9. C语言中怎么将文件里的数据创建到(读到)链表中?

    定义的结构体: struct student { ]; //学生学号 ]; //学生姓名 struct student *next; //next 指针 指向 struct student 类型的变量 ...

  10. 【转】Linux Mint 17.2 gedit中文乱码

    转自:linux mint 14 gedit 中文乱码 Mint默认没安装gconf-editor,搜了下,找到如下解决办法 在终端下执行语句: gconftool- --set --type=lis ...