1. 线程实现模型

线程的实现模型主要有3种:内核级线程模型、用户级线程模型和混合型线程模型。它们之间最大的区别在于线程与内核调度实体KSE(Kernel Scheduling Entity)之间的对应关系上。
所谓的内核调度实体KSE 就是指可以被操作系统内核调度器调度的对象实体,有些地方也称其为内核级线程,是操作系统内核的最小调度单元。

1.1 内核级线程模型

用户线程与KSE是1对1关系(1:1)。大部分编程语言的线程库(如linux的pthread,Java的java.lang.Thread,C++11的std::thread等等)都是对操作系统的线程(内核级线程)的一层封装,创建出来的每个线程与一个不同的KSE静态关联,因此其调度完全由OS调度器来做。这种方式实现简单,直接借助OS提供的线程能力,并且不同用户线程之间一般也不会相互影响。但其创建,销毁以及多个线程之间的上下文切换等操作都是直接由OS层面亲自来做,在需要使用大量线程的场景下对OS的性能影响会很大。

1.2 用户级线程模型

用户线程与KSE是多对1关系(M:1),这种线程的创建,销毁以及多个线程之间的协调等操作都是由用户自己实现的线程库来负责,对OS内核透明,一个进程中所有创建的线程都与同一个KSE在运行时动态关联。

现在有许多语言实现的协程 基本上都属于这种方式。这种实现方式相比内核级线程可以做的很轻量级,对系统资源的消耗会小很多,因此可以创建的数量与上下文切换所花费的代价也会小得多。但该模型有个致命的缺点,如果我们在某个用户线程上调用阻塞式系统调用(如用阻塞方式read网络IO),那么一旦KSE因阻塞被内核调度出CPU的话,剩下的所有对应的用户线程全都会变为阻塞状态(整个进程挂起)。
所以这些语言的协程库会把自己一些阻塞的操作重新封装为完全的非阻塞形式,然后在以前要阻塞的点上,主动让出自己,并通过某种方式通知或唤醒其他待执行的用户线程在该KSE上运行,从而避免了内核调度器由于KSE阻塞而做上下文切换,这样整个进程也不会被阻塞了。

1.3 混合型线程模型

用户线程与KSE是多对多关系(M:N), 这种实现综合了前两种模型的优点,为一个进程中创建多个KSE,并且线程可以与不同的KSE在运行时进行动态关联,当某个KSE由于其上工作的线程的阻塞操作被内核调度出CPU时,当前与其关联的其余用户线程可以重新与其他KSE建立关联关系。当然这种动态关联机制的实现很复杂,也需要用户自己去实现,这算是它的一个缺点吧。Go语言中的并发就是使用的这种实现方式,Go为了实现该模型自己实现了一个运行时调度器来负责Go中的"线程"与KSE的动态关联。此模型有时也被称为 两级线程模型,即用户调度器实现用户线程到KSE的“调度”,内核调度器实现KSE到CPU上的调度。

2. python 协程

python的协程,其实属于用户级别线程模型。

2.1 python2 协程

python2的协程源于yield指令,实现起来有点不容易理解。

yield有两个功能:

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

我们以一个经典的生产者、消费者模型举例:
生产者生产消息后,直接通过yield跳转到消费者开始执行
待消费者执行完毕后,切换回生产者继续生产

import timedef consumer():r = ''while True:n = yield r # consumer函数是一个generator(生成器),consumer通过yield拿到消息,处理,又通过yield把结果传回;if not n:returnprint('[CONSUMER] Consuming %s...' % n)time.sleep(1)r = '200 OK'def produce(c):c.next() # 首先调用c.next()启动生成器;n = 0while n < 5:n = n + 1print('[PRODUCER] Producing %s...' % n)r = c.send(n) # 一旦生产了东西,通过c.send(n)切换到consumer执行;print('[PRODUCER] Consumer return: %s' % r)c.close() # produce决定不生产了,通过c.close()关闭consumer,整个过程结束。if __name__=='__main__':c = consumer()produce(c)

执行结果

[PRODUCER] Producing 1...
[CONSUMER] Consuming 1...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 2...
[CONSUMER] Consuming 2...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 3...
[CONSUMER] Consuming 3...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 4...
[CONSUMER] Consuming 4...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 5...
[CONSUMER] Consuming 5...
[PRODUCER] Consumer return: 200 OK

整个流程无锁,由一个线程执行,produce和consumer协作完成任务,所以称为“协程”,而非线程的抢占式多任务。

2.2 python3 协程

实现协作式多任务,在Python3.5正式引入了 async/await表达式,使得协程正式在语言层面得到支持和优化,大大简化之前python2的yield写法。

import asyncioasync def compute(x, y):print("Compute %s + %s ..." % (x, y))await asyncio.sleep(x + y)return x + yasync def print_sum(x, y):result = await compute(x, y)print("%s + %s = %s" % (x, y, result))loop = asyncio.get_event_loop()
tasks = [print_sum(1, 2), print_sum(3, 4)]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

python协程,由语言的运行时中的 EventLoop(事件循环)来进行调度。和大多数语言一样,在 Python 中,协程的调度是非抢占式的,也就是说一个协程必须主动让出执行机会,其他协程才有机会运行。让出执行的关键字就是 await。也就是说一个协程如果阻塞了,持续不让出 CPU,那么整个线程就卡住了,没有任何并发。

作为服务端,event loop最核心的就是IO多路复用技术,所有来自客户端的请求都由IO多路复用函数来处理;

作为客户端,event loop的核心在于利用Future对象延迟执行,并使用send函数激发协程,挂起,等待服务端处理完成返回后再调用CallBack函数继续下面的流程。

3. go协程

go协程属于混合型线程模型,Go的并发用起来非常简单,用了一个语法糖将内部复杂的实现结结实实的包装了起来。

3.1 goroutine

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

package mainimport ("fmt""time"
)func f(from string) {for i := 0; i < 3; i++ {fmt.Println(from, ":", i)}
}func main() {f("direct")go f("goroutine")go func(msg string) {fmt.Println(msg)}("going")time.Sleep(time.Second)fmt.Println("done")
}

3.2 goroutine实现–G-P-M模型

G, P和M都是Go语言运行时系统(其中包括内存分配器,并发调度器,垃圾收集器等组件,可以想象为Java中的JVM)抽象出来概念和数据结构对象:

G:Goroutine的简称,上面用go关键字加函数调用的代码就是创建了一个G对象,是对一个要并发执行的任务的封装,也可以称作用户态线程。属于用户级资源,对OS透明,具备轻量级,可以大量创建,上下文切换成本低等特点。

M:Machine的简称,在linux平台上是用clone系统调用创建的,其与用linux pthread库创建出来的线程本质上是一样的,都是利用系统调用创建出来的OS线程实体。M的作用就是执行G中包装的并发任务。Go运行时系统中的调度器的主要职责就是将G公平合理的安排到多个M上去执行。其属于OS资源,可创建的数量上也受限了OS,通常情况下G的数量都多于活跃的M的。

P:Processor的简称,逻辑处理器,主要作用是管理G对象(每个P都有一个G队列),并为G在M上的运行提供本地化资源。

从1.3节介绍的两级线程模型来看,似乎并不需要P的参与,有G和M就可以了,那为什么要加入P这个东东呢?

其实Go语言运行时系统早期(Go1.0)的实现中并没有P的概念,Go中的调度器直接将G分配到合适的M上运行。但这样带来了很多问题:

例如,不同的G在不同的M上并发运行时可能都需向系统申请资源(如堆内存),由于资源是全局的,将会由于资源竞争造成很多系统性能损耗,为了解决类似的问题,后面的Go(Go1.1)运行时系统加入了P,让P去管理G对象,M要想运行G必须先与一个P绑定,然后才能运行该P管理的G

这样带来的好处是,我们可以在P对象中预先申请一些系统资源(本地资源),G需要的时候先向自己的本地P申请(无需锁保护),如果不够用或没有再向全局申请,而且从全局拿的时候会多拿一部分,以供后面高效的使用。就像现在我们去政府办事情一样,先去本地政府看能搞定不,如果搞不定再去中央,从而提供办事效率。

Go运行时系统通过构造G-P-M对象模型实现了一套用户态的并发调度系统,可以自己管理和调度自己的并发任务,所以可以说Go语言原生支持并发。自己实现的调度器负责将并发任务分配到不同的内核线程上运行,然后内核调度器接管内核线程在CPU上的执行与调度。

3.3 channel

Go 语言中最常见的、也是经常被人提及的设计模式就是 — 不要通过共享内存的方式进行通信,而是应该通过通信的方式(CSP)。

goroutine通过channel的方式进行通信,channel分为两种。

  • 同步 Channel — 不需要缓冲区,发送方会直接将数据交给(Handoff)接收方;
    默认情况下,通道是不带缓冲区的。发送端发送数据,同时必须有接收端相应的接收数据。
  • 异步 Channel — 基于环形缓存的传统生产者消费者模型;
    带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发送数据了。
ch <- v    // 发送值v到Channel ch中
v := <-ch  // 从Channel ch中接收数据,并将数据赋值给v

你可以把channel看成一个管道,它的操作符是箭头 <- ,箭头的指向就是数据的流向。

目前的 Channel 收发操作均遵循了先入先出(FIFO)的设计,具体规则如下:

  • 先从 Channel 读取数据的 Goroutine 会先接收到数据;
  • 先向 Channel 发送数据的 Goroutine 会得到先发送数据的权利;

以一个简单的channel应用开始,使用goroutine和channel实现一个任务队列,并行处理多个任务。

package mainimport ("fmt""time"
)func worker(id int, jobs <-chan int, results chan<- int) {for j := range jobs {fmt.Println("worker", id, "started  job", j)time.Sleep(time.Second)fmt.Println("worker", id, "finished job", j)results <- j * 2}
}func main() {const numJobs = 5jobs := make(chan int, numJobs)results := make(chan int, numJobs)for w := 1; w <= 3; w++ {go worker(w, jobs, results)}for j := 1; j <= numJobs; j++ {jobs <- j}close(jobs)for a := 1; a <= numJobs; a++ {<-results}
}

从上面的代码可以看出,使用golang的goroutine和channel可以很容易的实现一个生产者-消费者模式的任务队列,相比Java, c++简洁了很多。

同样,在golang中,阻塞/非阻塞、超时、同步等机制,利用channel都能很简单的实现。

参考:

进程和线程之间有什么根本性的区别?

python的协程与golang的协程有什么区别吗?总感觉不太一样,但又说不出哪里不一样?

事件驱动与协程:基本概念介绍

Go by Example: Worker Pools

Go Channel 详解

Go by Example

python coroutine,go routine对比--理解多线程、协程相关推荐

  1. python中协程的理解_python协程的理解

    一.介绍 什么是并发? 并发的本质就是切换+保存状态 cpu正在运行一个任务,会在两种情况下切走去执行其他的任务(切换由操作系统强制控制): 1.任务发生阻塞 2.计算任务时间过长,需要让出cpu给高 ...

  2. pdf 深入理解kotlin协程_协程初探

    Hello,各位朋友,小笨鸟我回来了! 近期学习了Kotlin协程相关的知识,感觉这块技术在项目中的可应用性很大,对项目的开发效率和维护成本有较大的提升.于是就考虑深入研究下相关概念和使用方式,并引入 ...

  3. 怎么更进一步学python_【百尺竿头,更进一步学Python】Python进阶课程——进程,线程和协程的区别...

    本文带来各类奇怪的IT百科知识. [百尺竿头,更进一步学Python]Python进阶课程--进程:线程和协程的区别 现在多进程多线程已经是老生常谈了:协程也在最近几年流行起来.今天我们本文主要介绍进 ...

  4. python模块介绍-gevent介绍:基于协程的网络库

    2019独角兽企业重金招聘Python工程师标准>>> python模块介绍-gevent介绍:基于协程的网络库 介绍 gevent是基于协程的Python网络库.特点: 基于lib ...

  5. python多线程协程配合使用_多线程配合协程

    协程配合线程 asyncio.run_coroutine_threadsafe 该方法的语法如下: asyncio.run_coroutine_threadsafe(coro, loop) 其实在协程 ...

  6. Python之路-python(Queue队列、进程、Gevent协程、Select\Poll\Epoll异步IO与事件驱动)

    一.进程: 1.语法 2.进程间通讯 3.进程池 二.Gevent协程 三.Select\Poll\Epoll异步IO与事件驱动 一.进程: 1.语法 1 简单的启动线程语法 2 def run(na ...

  7. python网络编程库_Python网络编程——协程

    协程的概念 协程,又称微线程,纤程,也称用户级线程,在不开辟线程的基础上实现多任务,也就是在单线程的情况下完成多任务,多个任务按照一定顺序交替执行的,通俗理解只要在def里面只看到一个yield关键字 ...

  8. 协程的概念及Python中利用第三方库gevent使用协程

    提到程序的并发操作,大多数人程序员首先想到的进程或者线程.我们先复习一下进程和线程的概念.   进程: 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的 ...

  9. Python中的线程、进程、协程以及区别

    进程,是执行中的计算机程序.也就是说,每个代码在执行的时候,首先本身即是一个进程.一个进程具有:就绪,运行,中断,僵死,结束等状态(不同操作系统不一样). 运行中每个进程都拥有自己的地址空间.内存.数 ...

最新文章

  1. OpenCV读写视频文件解析
  2. 【HDU】1305 Immediate Decodability(字典树:结构体数组,二维数组,链表/指针)
  3. 软件性能测试瓶颈定位,软件性能问题正确定位思路
  4. 【51NOD-0】1012 最小公倍数LCM
  5. MyBatisPlus条件构造器带条件查询selectList使用
  6. yum安装MariaDb10.2国内yum源配置
  7. php request entity too large,Nginx:413 Request Entity Too Large解决
  8. C#基础 基本语法4
  9. C++之函数模板探究
  10. Berkeley DB Java Edition
  11. ORACLE集群日志收集,【RAC】Oracle RAC集群环境下日志文件结构
  12. 电脑开机卡住了怎么办_苹果电脑忘记开机密码怎么办?一段代码轻松解决
  13. 后台事务自动跳转工作流节点
  14. python免费程序-Python——免费观看全网视频小程序
  15. python生成中文字符画_用python生成字符画
  16. 33岁学做软件测试还来得及? 4个建议送给你!
  17. 【ROS进阶篇】第九讲 基于Rviz和Arbotix控制的机器人模型运动
  18. 路由器和计算机的功能有何不同,网关和路由器的区别是什么 两者又有什么不同...
  19. arduino灯光装置_基于Arduino的智能家居灯控系统设计
  20. 【Tyvj1922】Freda的迷宫

热门文章

  1. 机器人图形变变变_幼儿园中班公开课数学教案《图形变变变》含反思
  2. 通过hashtable实现dic
  3. 各种池化操作(包括组合池化)
  4. java返回链表的中间结点_876. 链表的中间结点
  5. java ios 字符串_Java 与 iOS使用RSA 加密签名
  6. Java 并发编程之 Atomic 类
  7. c语言程序设计第二版课后答案 机械工业出版社,C语言程序设计 第2版
  8. python根据相关系数绘制热力图
  9. vue php tree,Vue 实现树形视图数据功能
  10. php strncmp,PHP中strncmp()函数比较两个字符串前2个字符是否相等的方法