深入浅析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构造函数及解构函数介绍
Feb 26 Python
Python __setattr__、 __getattr__、 __delattr__、__call__用法示例
Mar 06 Python
Python实现快速排序算法及去重的快速排序的简单示例
Jun 26 Python
对命令行模式与python交互模式介绍
May 12 Python
python 实现矩阵上下/左右翻转,转置的示例
Jan 23 Python
python实现微信机器人: 登录微信、消息接收、自动回复功能
Apr 29 Python
Django的Modelforms用法简介
Jul 27 Python
python-视频分帧&amp;多帧合成视频实例
Dec 10 Python
python中shell执行知识点
May 06 Python
python3判断IP地址的方法
Mar 04 Python
python运算符之与用户交互
Apr 13 Python
python开发制作好看的时钟效果
May 02 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&amp;&amp;mysql)三
2006/10/09 PHP
如何使用PHP往windows中添加用户
2006/12/06 PHP
用PHP实现的四则运算表达式计算实现代码
2011/08/02 PHP
php学习笔记之面向对象
2014/11/08 PHP
PHP面向对象之后期静态绑定功能介绍
2015/05/18 PHP
PHP PDOStatement::getAttribute讲解
2019/02/01 PHP
在 PHP 和 Laravel 中使用 Traits的方法
2019/11/13 PHP
jquery中animate动画积累的解决方法
2013/10/05 Javascript
JavaScript中的prototype.bind()方法介绍
2014/04/04 Javascript
ActiveX控件与Javascript之间的交互示例
2014/06/04 Javascript
javascript实现详细时间提醒信息效果的方法
2015/03/11 Javascript
自定义jQuery插件方式实现强制对象重绘的方法
2015/03/23 Javascript
jquery马赛克拼接翻转效果代码分享
2015/08/24 Javascript
浏览器兼容性问题大汇总
2015/12/17 Javascript
使用BootStrap建立响应式网页——通栏轮播图(carousel)
2016/12/21 Javascript
详解用vue编写弹出框组件
2017/07/04 Javascript
微信小程序实现获取自己所处位置的经纬度坐标功能示例
2017/11/30 Javascript
vue项目中v-model父子组件通信的实现详解
2017/12/10 Javascript
不到200行 JavaScript 代码实现富文本编辑器的方法
2018/01/03 Javascript
JavaScript作用域、闭包、对象与原型链概念及用法实例总结
2018/08/20 Javascript
vue基于element的区间选择组件
2018/09/07 Javascript
vue组件数据传递、父子组件数据获取,slot,router路由功能示例
2019/03/19 Javascript
详解微信小程序中var、let、const用法与区别
2020/01/11 Javascript
创建nuxt.js项目流程图解
2020/03/13 Javascript
vue在App.vue文件中监听路由变化刷新页面操作
2020/08/14 Javascript
Vue 3.0中jsx语法的使用
2020/11/13 Javascript
python 正则式使用心得
2009/05/07 Python
Python字符串匹配算法KMP实例
2015/07/18 Python
Python读写文件基础知识点
2019/06/10 Python
python3+PyQt5 自定义窗口部件--使用窗口部件样式表的方法
2019/06/26 Python
澳大利亚吉他在线:Artist Guitars
2017/03/30 全球购物
HearthSong官网:儿童户外玩具、儿童益智玩具
2017/10/16 全球购物
项目经理聘任书
2014/03/29 职场文书
无刑事犯罪记录证明范本
2014/09/29 职场文书
钱塘江大潮导游词
2015/02/03 职场文书
MySQL中LAG()函数和LEAD()函数的使用
2022/08/14 MySQL