深入浅析python 协程与go协程的区别


Posted in Python onMay 09, 2019

进程、线程和协程

进程的定义:

进程,是计算机中已运行程序的实体。程序本身只是指令、数据及其组织形式的描述,进程才是程序的真正运行实例。

线程的定义:

操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。

进程和线程的关系:

一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
CPU的最小调度单元是线程不是进程,所以单进程多线程也可以利用多核CPU.

协程的定义:

协程通过在线程中实现调度,避免了陷入内核级别的上下文切换造成的性能损失,进而突破了线程在IO上的性能瓶颈。

协程和线程的关系

协程是在语言层面实现对线程的调度,避免了内核级别的上下文消耗。

python协程与调度

Python的协程源于yield指令。yield有两个功能:

•yield item用于产出一个值,反馈给next()的调用方。
•作出让步,暂停执行生成器,让调用方继续工作,直到需要使用另一个值时再调用next()。

import asyncio
import datetime
async def display_date():
 loop = asyncio.get_running_loop()
 end_time = loop.time() + 5.0
 while True:
  print(datetime.datetime.now())
  if (loop.time() + 1.0) >= end_time:
   break
  await asyncio.sleep(1)
asyncio.run(display_date())

协程是对线程的调度,yield类似惰性求值方式可以视为一种流程控制工具,实现协作式多任务,在Python3.5正式引入了 Async/Await表达式,使得协程正式在语言层面得到支持和优化,大大简化之前的yield写法。

 线程是内核进行抢占式的调度的,这样就确保了每个线程都有执行的机会。
 

而 coroutine 运行在同一个线程中,由语言的运行时中的 EventLoop(事件循环)来进行调度。

 和大多数语言一样,在 Python 中,协程的调度是非抢占式的,也就是说一个协程必须主动让出执行机会,其他协程才有机会运行。

 让出执行的关键字就是 await。也就是说一个协程如果阻塞了,持续不让出 CPU,那么整个线程就卡住了,没有任何并发。

PS: 作为服务端,event loop最核心的就是IO多路复用技术,所有来自客户端的请求都由IO多路复用函数来处理;作为客户端,
event loop的核心在于利用Future对象延迟执行,并使用send函数激发协程,挂起,等待服务端处理完成返回后再调用CallBack函数继续下面的流程

Go的协程是天生在语言层面支持,和Python类似都是采用了关键字,而Go语言使用了go这个关键字,可能是想表明协程是Go语言中最重要的特性。

go协程之间的通信,Go采用了channel关键字。

Go实现了两种并发形式:

•多线程共享内存。如Java或者C++等在多线程中共享数据(例如数组、Map、或者某个结构体或对象)的时候,通过锁来访问.
•Go语言特有的,也是Go语言推荐的:CSP(communicating sequential processes)并发模型。

Go的CSP并发模型实现:M, P, G :

package main
import (
 "fmt"
)
//Go 协程(goroutines)和协程(coroutines)
//Go 协程意味着并行(或者可以以并行的方式部署),协程一般来说不是这样的
//Go 协程通过通道来通信;协程通过让出和恢复操作来通信
// 进程退出时不会等待并发任务结束,可用通道(channel)阻塞,然后发出退出信号
func main() {
 jobs := make(chan int)
 done := make(chan bool) // 结束标志
 go func() {
  for {
   j, more := <-jobs // 利用more这个值来判断通道是否关闭,如果关闭了,那么more的值为false,并且通知给通道done
   fmt.Println("----->:", j, more)
   if more {
    fmt.Println("received job", j)
   } else {
    fmt.Println("end received jobs")
    done <- true
    return
   }
  }
 }()
 go func() {
  for j := 1; j <= 3; j++ {
   jobs <- j
   fmt.Println("sent job", j)
  }
  close(jobs) // 写完最后的数据,紧接着就close掉
  fmt.Println("close(jobs)")
 }()
 fmt.Println("sent all jobs")
 <-done // 让main等待全部协程完成工作
}

通过在函数调用前使用关键字 go,我们即可让该函数以 goroutine 方式执行。goroutine 是一种 比线程更加轻盈、更省资源的协程。

Go 语言通过系统的线程来多路派遣这些函数的执行,使得 每个用 go 关键字执行的函数可以运行成为一个单位协程。
 当一个协程阻塞的时候,调度器就会自 动把其他协程安排到另外的线程中去执行,从而实现了程序无等待并行化运行。
 而且调度的开销非常小,一颗 CPU 调度的规模不下于每秒百万次,这使得我们能够创建大量的 goroutine,
 从而可以很轻松地编写高并发程序,达到我们想要的目的。 ---- 某书

协程的4种状态

•Pending
•Running
•Done
•Cacelled

和系统线程之间的映射关系

go的协程本质上还是系统的线程调用,而Python中的协程是eventloop模型实现,所以虽然都叫协程,但并不是一个东西.
Python 中的协程是严格的 1:N 关系,也就是一个线程对应了多个协程。虽然可以实现异步I/O,但是不能有效利用多核(GIL)。
 而 Go 中是 M:N 的关系,也就是 N 个协程会映射分配到 M 个线程上,这样带来了两点好处:

•多个线程能分配到不同核心上,CPU 密集的应用使用 goroutine 也会获得加速.
•即使有少量阻塞的操作,也只会阻塞某个 worker 线程,而不会把整个程序阻塞。

PS: Go中很少提及线程或进程,也就是因为上面的原因.

两种协程对比:

•async是非抢占式的,一旦开始采用 async 函数,那么你整个程序都必须是 async 的,不然总会有阻塞的地方(一遇阻塞对于没有实现异步特性的库就无法主动让调度器调度其他协程了),也就是说 async 具有传染性。
•Python 整个异步编程生态的问题,之前标准库和各种第三方库的阻塞性函数都不能用了,requests 不能用了,redis.py 不能用了,甚至 open 函数都不能用了。所以 Python 协程的最大问题不是不好用,而是生态环境不好。
•goroutine 是 go 与生俱来的特性,所以几乎所有库都是可以直接用的,避免了 Python 中需要把所有库重写一遍的问题。
•Goroutine 中不需要显式使用 await 交出控制权,但是 Go 也不会严格按照时间片去调度 goroutine,而是会在可能阻塞的地方插入调度。Goroutine 的调度可以看做是半抢占式的。

PS: python异步库列表 [https://github.com/timofurrer/awesome-asyncio]

--------------------------------------------------------------------------------

Do not communicate by sharing memory; instead, share memory by communicating.(不要以共享内存的方式来通信,相反,要通过通信来共享内存) -- CSP并发模型

--------------------------------------------------------------------------------

扩展与总结

erlang和golang都是采用了CSP(Communicating Sequential Processes)模式(Python中的协程是eventloop模型)
但是erlang是基于进程的消息通信,go是基于goroutine和channel的通信。
Python和Go都引入了消息调度系统模型,来避免锁的影响和进程/线程开销大的问题。
 协程从本质上来说是一种用户态的线程,不需要系统来执行抢占式调度,而是在语言层面实现线程的调度。
 因为协程不再使用共享内存/数据,而是使用通信来共享内存/锁,因为在一个超级大系统里具有无数的锁,
 共享变量等等会使得整个系统变得无比的臃肿,而通过消息机制来交流,可以使得每个并发的单元都成为一个独立的个体,
 拥有自己的变量,单元之间变量并不共享,对于单元的输入输出只有消息。
 开发者只需要关心在一个并发单元的输入与输出的影响,而不需要再考虑类似于修改共享内存/数据对其它程序的影响。

Python 相关文章推荐
Python实现多线程抓取妹子图
Aug 08 Python
Python采用Django制作简易的知乎日报API
Aug 03 Python
Python logging管理不同级别log打印和存储实例
Jan 19 Python
python OpenCV学习笔记之绘制直方图的方法
Feb 08 Python
python实现可视化动态CPU性能监控
Jun 21 Python
Python实现KNN(K-近邻)算法的示例代码
Mar 05 Python
24式加速你的Python(小结)
Jun 13 Python
Django MEDIA的配置及用法详解
Jul 25 Python
django3.02模板中的超链接配置实例代码
Feb 04 Python
详解django中Template语言
Feb 22 Python
python pyqtgraph 保存图片到本地的实例
Mar 14 Python
Python学习之异常中的finally使用详解
Mar 16 Python
Python实现堡垒机模式下远程命令执行操作示例
May 09 #Python
python3.6使用tkinter实现弹跳小球游戏
May 09 #Python
使用GitHub和Python实现持续部署的方法
May 09 #Python
在win10和linux上分别安装Python虚拟环境的方法步骤
May 09 #Python
Python Excel处理库openpyxl使用详解
May 09 #Python
python3实现小球转动抽奖小游戏
Apr 15 #Python
Django保护敏感信息的方法示例
May 09 #Python
You might like
十天学会php(3)
2006/10/09 PHP
浅谈php serialize()与unserialize()的用法
2013/06/05 PHP
php对数组排序代码分享
2014/02/24 PHP
php实现图片局部打马赛克的方法
2015/02/11 PHP
PHP消息队列用法实例分析
2016/02/12 PHP
jQuery中判断一个元素是否为另一个元素的子元素(或者其本身)
2012/03/21 Javascript
JavaScript 操作table,可以新增行和列并且隔一行换背景色代码分享
2013/07/05 Javascript
JavaScript实现点击按钮后变灰避免多次重复提交
2013/07/15 Javascript
jqgrid 编辑添加功能详细解析
2013/11/08 Javascript
JavaScript中的Promise使用详解
2015/06/24 Javascript
基于BootStrap的Metronic框架实现页面链接收藏夹功能按钮移动收藏记录(使用Sortable进行拖动排序)
2016/08/29 Javascript
Webpack常见静态资源处理-模块加载器(Loaders)+ExtractTextPlugin插件
2017/06/29 Javascript
ES6学习教程之模板字符串详解
2017/10/09 Javascript
利用VS Code开发你的第一个AngularJS 2应用程序
2017/12/15 Javascript
微信小程序实现获取用户信息并存入数据库操作示例
2019/05/07 Javascript
在Layui中操作数据表格,给指定单元格添加事件示例
2019/10/26 Javascript
JavaScript this关键字的深入详解
2021/01/14 Javascript
vue实现轮播图帧率播放
2021/01/26 Vue.js
Python实现115网盘自动下载的方法
2014/09/30 Python
python使用7z解压apk包的方法
2015/04/18 Python
python抓取并保存html页面时乱码问题的解决方法
2016/07/01 Python
Python远程开发环境部署与调试过程图解
2019/12/09 Python
python实现的分析并统计nginx日志数据功能示例
2019/12/21 Python
Python3 ID3决策树判断申请贷款是否成功的实现代码
2020/05/21 Python
Python多个装饰器的调用顺序实例解析
2020/05/22 Python
基于Python实现简单学生管理系统
2020/07/24 Python
python空元组在all中返回结果详解
2020/12/15 Python
利用python绘制正态分布曲线
2021/01/04 Python
中科创达面试题
2016/12/28 面试题
报关报检委托书
2014/04/08 职场文书
生产工厂门卫岗位职责
2014/09/26 职场文书
2014年保管员工作总结
2014/11/18 职场文书
中秋节晚会开场白
2015/05/29 职场文书
使用Oracle跟踪文件的问题详解
2021/06/28 Oracle
Spring Boot配合PageHelper优化大表查询数据分页
2022/04/20 Java/Android
Java界面编程实现界面跳转
2022/06/16 Java/Android