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

进程:

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

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

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

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

线程:

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

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

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

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

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

如:

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

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

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

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

协程:

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

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

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

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

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

 #!/usr/bin/env python
# -*- coding:utf-8 -*- import time def consumer():
r = ''
while True:
n = yield r
if not n:
return
print('Consume running %s...' % n)
time.sleep(1) #遇到阻塞到produce执行
r = '200 OK' def produce(c):
c.next() #启动迭代器
n = 0
while n < 5:
n = n + 1
print('[Produce] running %s...' % n)
r = c.send(n) #到consumer中执行
print('[Consumer] return: %s' % r)
c.close() if __name__=='__main__':
c = consumer() #迭代器
produce(c) 执行结果:
[Produce] running 1...
Consume running 1...
[Consumer] return: 200 OK
[Produce] running 2...
Consume running 2...
[Consumer] return: 200 OK
[Produce] running 3...
Consume running 3...
[Consumer] return: 200 OK
[Produce] running 4...
Consume running 4...
[Consumer] return: 200 OK
[Produce] running 5...
Consume running 5...
[Consumer] return: 200 OK

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

 #!/usr/bin/env python
# -*- coding:utf-8 -*- #封装好的协成
from greenlet import greenlet def test1():
print "test1:",11
gr2.switch()
print "test1:",12
gr2.switch() def test2():
print "test2:",13
gr1.switch()
print "test2:",14 gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch() 执行结果:
test1: 11
test2: 13
test1: 12
test2: 14

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

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

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

 #!/usr/bin/env python
# -*- coding:utf-8 -*- #协成的自动切换
import gevent
import time def func1():
print('\033[31;1m 正在执行 111...\033[0m')
gevent.sleep(2)
print('\033[31;1m 正在执行 444...\033[0m') def func2():
print('\033[32;1m 正在执行 222...\033[0m')
gevent.sleep(3) #阻塞3秒,所以自动切换到func1,执行完func1后 再切换回来
print('\033[32;1m 正在执行 333...\033[0m') start_time = time.time()
gevent.joinall([
gevent.spawn(func1),
gevent.spawn(func2),
# gevent.spawn(func3),
])
end_time = time.time() #程序总共花费3秒执行
print "spend",(end_time-start_time),"second" 执行结果:
正在执行 111...
正在执行 222...
正在执行 444...
正在执行 333...
总耗时:
spend 3.00936698914 second

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

client端:

 #!/usr/bin/env python
# -*- coding:utf-8 -*- from socket import * ADDR, PORT = 'localhost', 8001
client = socket(AF_INET,SOCK_STREAM)
client.connect((ADDR, PORT)) while 1:
cmd = raw_input('>>:').strip()
if len(cmd) == 0: continue
client.send(cmd)
data = client.recv(1024)
print data
#print('Received', repr(data)) client.close()

server端:

 #!/usr/bin/env python
# -*- coding:utf-8 -*- import sys
import socket
import gevent
from gevent import monkey
monkey.patch_all() def server(port):
sock = socket.socket()
sock.bind(('127.0.0.1', port))
sock.listen(500)
while 1:
conn, addr = sock.accept()
#handle_request(conn)
gevent.spawn(handle_request, conn) def handle_request(conn):
try:
while 1:
data = conn.recv(1024)
if not data:
break
print("recv:",data)
conn.send(data) except Exception as ex:
print(ex)
finally:
conn.close() if __name__ == '__main__':
server(8001)

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

OVER!

python中协程的更多相关文章

  1. 深入理解Python中协程的应用机制: 使用纯Python来实现一个操作系统吧!!

    本文参考:http://www.dabeaz.com/coroutines/   作者:David Beazley 缘起: 本人最近在学习python的协程.偶然发现了David Beazley的co ...

  2. python中协程实现的本质以及两个封装协程模块greenle、gevent

    协程 协程,又称微线程,纤程.英文名Coroutine. 协程是啥 协程是python个中另外一种实现多任务的方式,只不过比线程更小占用更小执行单元(理解为需要的资源). 为啥说它是一个执行单元,因为 ...

  3. Python中协程的实现

    通过关键字yield,可以从生成器中产生值,并返回.我们可以将生成器作为一个生产者来使用. 在协程中,通过使用关键字yield,还可以让具有yield的程序接收值.此时函数作为消费者,消费我们传入(s ...

  4. python中协程的使用示例

    例子1 把字符串分割为列表 def line_splitter( delimiter = None ): print( 'ready to split' ) result = None while T ...

  5. Python中协程Event()函数

    python线程的事件用于主线程控制其他线程的执行,事件主要提供了三个方法wait.clear.set 事件处理的机制:全局定义了一个“Flag”,如果“Flag”值为 False,那么当程序执行 e ...

  6. python gevent 协程

    简介 没有切换开销.因为子程序切换不是线程切换,而是由程序自身控制,没有线程切换的开销,因此执行效率高, 不需要锁机制.因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断 ...

  7. {python之协程}一 引子 二 协程介绍 三 Greenlet 四 Gevent介绍 五 Gevent之同步与异步 六 Gevent之应用举例一 七 Gevent之应用举例二

    python之协程 阅读目录 一 引子 二 协程介绍 三 Greenlet 四 Gevent介绍 五 Gevent之同步与异步 六 Gevent之应用举例一 七 Gevent之应用举例二 一 引子 本 ...

  8. 【Python】协程

    协程,又称微线程,纤程.英文名Coroutine. 协程的概念很早就提出来了,但直到最近几年才在某些语言(如Lua)中得到广泛应用. 子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在 ...

  9. Python之协程(coroutine)

    Python之协程(coroutine) 标签(空格分隔): Python进阶 coroutine和generator的区别 generator是数据的产生者.即它pull data 通过 itera ...

随机推荐

  1. 承接 AutoCAD 二次开发 项目

    本人有多年的CAD开发经验,独立完成多个CAD二次开发项目.熟悉.net及Asp.net开发技术,和Lisp开发技术. 现在成立了工作室,独立承接CAD二次开发项目.结项后提供源码及开发文档,有需要的 ...

  2. 用户名 不在 sudoers文件中

    切换到root用户,然后加上某个账户 pzdn@CentOs$ su #输入root密码 root@CentOs cd /etc/ chmod 770 sudoers vim sudoers # 找到 ...

  3. 关于android 加载https网页的问题

    我在加载https网页时出现空白, 因此,我就百度一下,可以发现: webView.setWebViewClient(new WebViewClient(){ @Override public voi ...

  4. 【SAP BO】无法识别账户信息:无法访问CMS。计算机上的CMS由于某个严重错误而停止。(FWM 20031)

    1.系统环境 OS:Windows Server 2008 R2 RDBMS:Oracle 11g R2(Server.Client同时存在) BI:SAP Business Objects 4.2 ...

  5. 运行jar应用程序引用其他jar包的四种方法

    转载地址:http://www.iteye.com/topic/332580 大家都知道一个java应用项目可以打包成一个jar,当然你必须指定一个拥有main函数的main class作为你这个ja ...

  6. block 从B界面向A界面传值

    最近在改公司外包项目的代码,发现了一种block传值的用法很有意思,记录一下 A.B两个页面 在B界面.h中定义 @property (nonatomic,strong) void(^block)(N ...

  7. sublime 编辑器配置和构建检查

    sublime3插件 安装node包 jscs npm install jscs -g jshint npm install jshint -g csscomb npm install csscomb ...

  8. 关于Hibernate的Dialect

    org.hibernate HibernateException Dialect must be explicitly set :***  使用Hibernate,有时候会遇到类似上面的异常.  使用 ...

  9. Ubuntu14.04或16.04下安装JDK1.8+Scala+Hadoop2.7.3+Spark2.0.2

    为了将Hadoop和Spark的安装简单化,今日写下此帖. 首先,要看手头有多少机器,要安装伪分布式的Hadoop+Spark还是完全分布式的,这里分别记录. 1. 伪分布式安装 伪分布式的Hadoo ...

  10. springMVC+spring+hibernate 框架整合实例

    先说一下流程思路: 流程讲解1:首先访问会先定位到控制器.这就用到了过滤器配置文件"spring-mvc.xml".这个文件负责定义控制器的包路径.视图的格式等.其次从" ...