linux系统中,处理器总处于以下状态中的一种:
一个进程的上下文可以分为三个部分:用户级上下文、寄存器上下文以及系统级上下文。
当发生进程调度时,进行进程切换就是上下文切换(context switch).操作系统必须对上面提到的全部信息进行切换,新调度的进程才能运行。
就时间开销而言,Linux 下创建进程跟线程差不多,事实上创建线程的时间开销略微大一点点。
就空间开销而言,Linux 下创建进程总是有开销的,而且显然会略大于线程。
事件驱动模型还有另外一个名字,而且更加出名的名字:io多路复用。
在最开始的时候,为了实现一个服务器可以支持多个客户端连接,人们想出了fork/thread等办法,当一个连接来到的时候,就fork/thread一个进程/线程去接收并且处理请求。
当客户端连接越来越多,多进程/多线程模型无法承受了。因为进程/线程切换的开销太大。IO多路复用机制(即事件驱动架构)被发明出来了,简单的说就是由一些事件发生源来产生事件,由事件收集器来收集、分发事件,然后由事件处理器来处理这些事件(事件处理器需要先在事件收集器里注册自己想处理的事件)。
多个客户端连接的情况下,事件驱动虽然可以提高效率,但是事件驱动模型本身是无法利用多核CPU的。所以高性能web服务器一般采用多进程/多线程+事件驱动一起使用(比如nginx)。
python的协程,其实就是一种io多路复用。
python2的协程源于yield指令,实现起来有点不容易理解。
yield有两个功能:
我们以一个经典的生产者、消费者模型举例:
生产者生产消息后,直接通过yield跳转到消费者开始执行
待消费者执行完毕后,切换回生产者继续生产
import time
def consumer():
r = ''
while True:
n = yield r # consumer函数是一个generator(生成器),consumer通过yield拿到消息,处理,又通过yield把结果传回;
if not n:
return
print('[CONSUMER] Consuming %s...' % n)
time.sleep(1)
r = '200 OK'
def produce(c):
c.next() # 首先调用c.next()启动生成器;
n = 0
while n < 5:
n = n + 1
print('[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协作完成任务,所以称为“协程”,而非线程的抢占式多任务。
实现协作式多任务,在Python3.5正式引入了 async/await表达式,使得协程正式在语言层面得到支持和优化,大大简化之前python2的yield写法。
import asyncio
async def compute(x, y):
print("Compute %s + %s ..." % (x, y))
await asyncio.sleep(x + y)
return x + y
async 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函数继续下面的流程。
Golang的一大特色就是其简单高效的天然并发机制,使用goroutine和channel实现了CSP模型。
Go天生在语言层面支持,和Python类似都是采用了关键字,而Go语言使用了go这个关键字,可能是想表明协程是Go语言中最重要的特性。
package main
import (
"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")
}
python coroutine和goroutine 虽然都叫做协程,但其实完全不一样。
python coroutine 是严格的 1:N 关系,也就是一个线程对应了多个协程。多个协程运行在同一个线程中。同一时刻其实只有一段代码运行,并没有真正实现并行,只能算是并发。而且在用 asyncio 时,必须要使用异步库。
goroutine 是真正意义上的并行,是 M:N 的关系,也就是 N 个协程会映射分配到 M 个线程上。goroutine 有两点好处:
Go 语言中最常见的、也是经常被人提及的设计模式就是 — 不要通过共享内存的方式进行通信,而是应该通过通信的方式(CSP)。
goroutine通过channel的方式进行通信,channel分为两种。
ch <- v // 发送值v到Channel ch中
v := <-ch // 从Channel ch中接收数据,并将数据赋值给v
你可以把channel看成一个管道,它的操作符是箭头 <- ,箭头的指向就是数据的流向。
目前的 Channel 收发操作均遵循了先入先出(FIFO)的设计,具体规则如下:
以一个简单的channel应用开始,使用goroutine和channel实现一个任务队列,并行处理多个任务。
package main
import (
"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 = 5
jobs := 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