协程与线程

协程(Coroutine)是什么?为什么需要协程(Coroutine)?

Coroutine是编译器级的,Process和Thread是操作系统级的。 对于Coroutine,是编译器帮助做了很多的事情,来让代码不是一次性的跑到底,而不是操作系统强制的挂起。代码每次跑多少,是可预期的。但是,Process和Thread,在这个层面上完全不同,这两个东西是操作系统管理的。

实现

  • Coroutine的实现,通常是对某个语言做相应的提议,然后通过后成编译器标准,然后编译器厂商来实现该机制。
  • Process和Thread看起来也在语言层次,但是内生原理却是操作系统先有这个东西,然后通过一定的API暴露给用户使用,两者在这里有不同。

调度

  • Process和Thread是os通过调度算法,保存当前的上下文,然后从上次暂停的地方再次开始计算,重新开始的地方不可预期,每次CPU计算的指令数量和代码跑过的CPU时间是相关的,跑到os分配的cpu时间到达后就会被os强制挂起。

  • Coroutine是编译器的魔术,通过插入相关的代码使得代码段能够实现分段式的执行,重新开始的地方是yield关键字指定的,一次一定会跑到一个yield对应的地方

调用切换所耗资源

  1. 进程调度,切换进程上下文,包括分配的内存,包括数据段,附加段,堆栈段,代码段,以及一些表格。
  2. 线程调度,切换线程上下文,主要切换堆栈,以及各寄存器,因为同一个进程里的线程除了堆栈不同。
  3. 协程又称为轻量级线程,每个协程都自带了一个栈,可以认为一个协程就是一个函数和这个存放这个函数运行时数据的栈,这个栈非常小,一般只有几十kb。
    协程与线程_第1张图片

协程与IO

IO密集型为什么用线程不好

当涉及到大规模的并发连接时,例如10K连接。以线程作为处理单元,系统调度的开销还是过大。当连接数很多 —> 需要大量的线程来干活 —> 可能大部分的线程处于ready状态 —> 系统会不断地进行上下文切换。 可见性能瓶颈在上下文切换。

协程出现

协程的出现出现为克服同步模型和异步模型的缺点,并结合他们的优点提供了可能: 现在假设我们有3个协程A,B,C分别要进行数次IO操作。

这3个协程运行在同一个调度器或者说线程的上下文中,并依次使用CPU。 调度器在其内部维护了一个多路复用器(epoll/select/poll)。

  1. 协程A首先运行,当它执行到一个IO操作,但该IO操作并没有立即就绪时,A将该IO事件注册到调度器中,并主动放弃CPU。
  2. 这时调度器将B切换到CPU上开始执行,同样,当它碰到一个IO操作的时候将IO事件注册到调度器中,并主动放弃CPU。
  3. 调度器将C切换到cpu上开始执行。
  4. 当所有协程都被“阻塞”后,调度器检查注册的IO事件是否发生或就绪。**假设此时协程B注册的IO时间已经就绪,调度器将恢复B的执行,B将从上次放弃CPU的地方接着向下运行。**A和C同理。

这样,对于每一个协程来说,它是同步的模型;但是对于整个应用程序来说,它是异步的模型。 意味着

  • 协程访问全局变量是不需要加锁的,因为一个个协程轮流运行
  • 在协程轮流执行的过程中如果一个协程进入死循环,或者陷入内核态,那么其他的协程也会被堵死。

进程 协程 线程的选择情景

  • 多核CPU,CPU密集型应用
    此时多线程的效率是最高的,多线程可以使到全部CPU核心满载,又避免了协程间切换造成性能损失。当CPU密集型任务时,CPU一直在利用着,切换反而会造成性能损失,即便协程上下文切换消耗最小,但也还是有消耗的。

  • 多核CPU,IO密集型应用
    此时采用多线程多协程效率最高,多线程可以使到全部CPU核心满载,而一个线程多协程,则更好的提高了CPU的利用率。

  • 单核CPU,CPU密集型应用
    单进程效率是最高,此时单个进程已经使到CPU满载了。

  • 单核CPU,IO密集型应用(上下文切换是瓶颈)
    多协程,效率最高。例如,看了上面应该也是知道的了

例子

传统的生产者-消费者模型是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待,但一不小心就可能死锁。

如果改用协程,生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产,效率极高:

1.用多线程的思路

import threading, time
import Queue    #导入消息队列模块
import random   #导入随机数模块,是为了模拟生产者与消费者速度不一致的情形
q = Queue.Queue()    #实例化一个对象
 
def Producer(name):          #生产者函数
    for i in range(20):  
        q.put(i)     #将结果放入消息队列中
        print '\033[32;1mProducer %s has made %s baozi....\033[0m' % (name, i)
        time.sleep(random.randrange(3))    #生产者的生产速度,3s内
def Consumer(name):          #消费者函数
    count = 0
    while count < 20:
        data = q.get()    #取用消息队列中存放的结果
        print '\033[31;1mConsumer %s has eatem %s baozi...chihuo...\033[0m' % (name, data)
        count += 1
        time.sleep(random.randrange(4))    #消费者的消费速度,4s内
 
p = threading.Thread(target = Producer, args = ('Alex',))
c = threading.Thread(target = Consumer, args = ('Wangfanhao',))
 
p.start()
c.start()

2.用协程的思路,可以回顾一下python中的yield的用法

def consumer():
    r = ''
    while True:
        n = yield r
        if not n:
            return
        print('[CONSUMER] Consuming %s...' % n)
        r = '200 OK'

def produce(c):
    c.send(None)
    n = 0
    while n < 5:
        n = n + 1
        print('[PRODUCER] Producing %s...' % n)
        r = c.send(n)
        print('[PRODUCER] Consumer return: %s' % r)
    c.close()

c = consumer()
produce(c)

你可能感兴趣的:(操作系统,2018,多线程)