123 协程基础

一、线程、进程回顾

  1. 在操作系统中进程是资源分配的最小单位,线程是CPU调度的最小单位。
  2. 并发的本质:切换+保存状态。
  3. cpu正在运行一个任务,会在两种情况下切走去执行其他的任务(切换由操作系统强制控制),一种情况是该任务发生了阻塞,另外一种情况是该任务计算的时间过长。
  4. 在介绍进程理论时,提及进程的三种执行状态,而线程才是执行单位,所以也可以将上图理解为线程的三种状态。
  5. 其中并发并不能提升效率,只是为了让cpu能够雨露均沾,实现看起来所有任务都被“同时”执行的效果,如果多个任务都是纯计算的,这种切换反而会降低效率。

二、协程介绍

协程:是单线程下的并发,又称微线程,纤程。英文名Coroutine。

一句话说明什么是协程:协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的,单线程下实现并发。

需要强调的是

  1. python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行)
  2. 单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(!!!非io操作的切换与效率无关)

对比操作系统控制线程的切换,用户在单线程内控制协程的切换。

重点:遇到io切换的时候才有意义

具体: 协程概念本质是程序员抽象出来的,操作系统根本不知道协程存在,也就说来了一个线程我自己遇到io 我自己线程内部直接切到自己的别的任务上了,操作系统跟本发现不了,也就是实现了单线程下效率最高.

优点

  1. 协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级
  2. 单线程内就可以实现并发的效果,最大限度地利用cpu

缺点

  1. 协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程,自己要检测所有的io,但凡有一个阻塞整体都跟着阻塞.
  2. 协程指的是单个线程,因而一旦协程出现一个阻塞,没有切换任务,将会阻塞整个线程

特点

  1. 必须在只有一个单线程里实现并发
  2. 修改共享数据不需加锁
  3. 用户程序里自己保存多个控制流的上下文栈
import time
def eat():
    print('eat 1')
    # 疯狂的计算呢没有io
    time.sleep(2)
    # for i in range(100000000):
    #     i+1
def play():
    print('play 1')
    # 疯狂的计算呢没有io
    time.sleep(3)
    # for i in range(100000000):
    #     i+1
play()
eat() # 5s

在单线程里,利用yield来实现协程,这是一个没有意义的携程(因为我们说过协程要做在有io的情况下才有意义)

import time
def func1():
    while True:
        1000000+1
        yield

def func2():
    g = func1()
    for i in range(100000000):
        i+1
        next(g)

start = time.time()
func2()
stop = time.time()
print(stop - start) # 17.68560242652893

对比上面yeild切换运行的时间,反而比我们单独取执行函数串行更消耗时间,所以上面实现的携程是没有意义的。

import time

def func1():
    for i in range(100000000):
        i+1
def func2():
    for i in range(100000000):
        i+1

start = time.time()
func1()
func2()
stop = time.time()
print(stop - start) # 12.08229374885559

三、协程的本质

协程的本质就是在单线程下,由用户自己控制一个任务遇到io阻塞了就切换另外一个任务去执行,以此来提升效率。为了实现它,我们需要找寻一种可以同时满足以下条件的解决方案:

  1. 可以控制多个任务之间的切换,切换之前将任务的状态保存下来,以便重新运行时,可以基于暂停的位置继续执行。
  2. 作为1的补充:可以检测io操作,在遇到io操作的情况下才发生切换

3.1 使用协程我们需要用到genvent模块

重点:使用gevent来实现协程是可以的,但是我们说过协程最主要是遇到IO才有意义,但是恰好这个gevent模块做不到协程的真正的意义,也就是说这个而模块他检测不到IO

但用gevent模块是检测不到IO的,也就是说这样写同样是没有意义的

下面程序里的gevent是一个类

  1. gevent.spawn本质调用了gevent.greenlet.Greenlet的类的静态方法spawn:

    @classmethod
    def spawn(cls, *args, **kwargs):
        g = cls(*args, **kwargs)
        g.start()
        return g
  2. 这个类方法调用了Greenlet类的两个函数,*__init_*_ 和 start. init函数中最为关键的是这段代码: 
    def __init__(self, run=None, *args, **kwargs):
       greenlet.__init__(self, None, get_hub()) # 将新创生的greenlet实例的parent一律设置成hub
       if run is not None:
       self._run = run
# 在这段程序我们发现,这段程序并没有实现遇见IO的时候,用户模cpu实现任务切换
import gevent
import time

def eat():
    print('eat 1')
    time.sleep(2)
    print('eat 2')
def play():
    print('play 1')
    # 疯狂的计算呢没有io
    time.sleep(3)
    print('play 2')

start = time.time()
g1 = gevent.spawn(eat)
g2 = gevent.spawn(play)
g1.join()
g2.join()
end = time.time()
print(end-start) 5.0041022300720215

'''
结果:
eat 1
eat 2
play 1
play 2
5.004306077957153
'''

重点二:使用gevent的一个补丁来实现,通过gevent类来实现真正有意义的协程,用户真正的实现里以操作系统发现不了的方式,模拟了遇见IO的时候实现任务之间的来回切换

注意:这里再次强调,协程的本质意义是在单线程内实现任务的保存状态加切换,并且真正的协程必须是在遇到IO的情况

from gevent import monkey;monkey.patch_all()
import gevent
import time

def eat():
    print('eat 1')
    time.sleep(2)
    print('eat 2')
def play():
    print('play 1')
    # 疯狂的计算呢没有io
    time.sleep(3)
    print('play 2')

start = time.time()
g1 = gevent.spawn(eat)
g2 = gevent.spawn(play)
g1.join()
g2.join()
end = time.time()
print(end-start)# 3.003168821334839

'''
结果:
eat 1
play 1
eat 2
play 2
3.003168821334839
'''

原文地址:https://www.cnblogs.com/xichenHome/p/11569119.html

时间: 2024-10-14 06:34:52

123 协程基础的相关文章

协程基础

协程基础 一.引言 之前我们学习了线程.进程的概念,了解了在操作系统中进程是资源分配的最小单位,线程是CPU调度的最小单位.按道理来说我们已经算是把CPU的利用率提高很多了.但是我们知道无论是创建多进程还是创建多线程来解决问题,都要消耗一定的时间来创建进程.创建线程.以及管理他们之间的切换. 随着我们对于效率的追求不断提高,基于单线程来实现并发又成为一个新的课题,即只用一个主线程(很明显可利用的cpu只有一个)情况下实现并发.这样就可以节省创建线进程所消耗的时间. 为此我们需要先回顾下并发的本质

协程基础_context系列函数

近期想看看协程,对这个的详细实现不太了解.查了下,协程最常规的做法就是基于makecontext,getcontext,swapcontext这类函数在用户空间切换用户上下文. 所以在这通过样例代码尽量把context相关的函数弄清楚先. #include <ucontext.h> #include <stdio.h> #include <stdlib.h> static ucontext_t uctx_main, uctx_func1, uctx_func2; #de

协程基础及其创建和使用方法

一.引言 之前我们学习了线程.进程的概念,了解了在操作系统中进程是资源分配的最小单位,线程是CPU调度的最小单位.按道理来说我们已经算是把cpu的利用率提高很多了.但是我们知道无论是创建多进程还是创建多线程来解决问题,都要消耗一定的时间来创建进程.创建线程.以及管理他们之间的切换. 随着我们对于效率的追求不断提高,基于单线程来实现并发又成为一个新的课题,即只用一个主线程(很明显可利用的cpu只有一个)情况下实现并发.这样就可以节省创建线进程所消耗的时间. 所以,产生协程的概念是为了实现: 单线程

最轻量级的C协程库:Protothreads

@20150228 http://blog.csdn.net/weiwangchao_/article/details/7777385 协程的好处不用再多说,作为与函数调用/返回相对的概念,它使我们思考问题的方式经历一场变革.现在我们关注的是C,由于C本身的特质,将协程引入其中将会是一 个挑战.无数先驱已经为这个目标抛了头颅洒了热血,于是我们有了libtask之类.而这里提到的,是一个堪称最轻量级的协程实现:Protothreads(主页:http://www.sics.se/~adam/pt/

pythonNet 09协程

前情回顾1. 进程线程的区别和联系   * 都是多任务编程   * 一个进程包含多个线程   * 都是动态的占有资源的,线程共享进程的资源   * 进程比线程消耗资源更多   * 进程空间独立使用特定的IPC,线程使用全局变量 2. 服务器模型      循环模型 : 同一时刻只能处理一个请求 并发模型 : IO 并发 : 多个IO任务               多进程/多线程并发 : 任何任务  3. 基于fork的多进程并发程序    每当有一个客户端连接就创建一个新的进程 4. ftp文

Python基础—线程、进程和协程

今天已是学习Python的第十一天,来干一碗鸡汤继续今天的内容,今天的鸡汤是:超越别人对你的期望.本篇博客主要介绍以下几点内容: 线程的基本使用: 线程的锁机制: 生产者消费之模型(队列): 如何自定义线程池: 进程的基本使用: 进程的锁机制: 进程之间如何实现数据共享: 进程池: 协程的基本使用. 一.线程 1.创建线程 上篇博客已经介绍过如何创建多线程的程序,在这里在复习一下如何创建线程过程以及线程的一些方法: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 1

Python菜鸟之路:Python基础-线程、进程、协程

上节内容,简单的介绍了线程和进程,并且介绍了Python中的GIL机制.本节详细介绍线程.进程以及协程的概念及实现. 线程 基本使用 方法1: 创建一个threading.Thread对象,在它的初始化函数(__init__)中将可调用对象作为参数传入 import threading import time def worker(): time.sleep(2) print("test") for i in range(5): t = threading.Thread(target=

FreeRTOS基础以及UIP之协程--C语言剑走偏锋

在FreeRTOS中和UIP中,都使用到了一种C语言实现的多任务计数,专业的定义叫做协程(coroutine),顾名思义,这是一种协作的例程, 跟具有操作系统概念的线程不一样,协程是在用户空间利用程序语言的语法语义就能实现逻辑上类似多任务的编程技巧. 意思就是说协程不需要每次调用的时候都为任务准备一次空间,我们知道像ucos这种操作系统,它内置的多任务是需要在中断过程中切换堆栈的,开销较大,而协程的功能就是在尽量降低开销的情况下,实现能够保存函数上下文快速切换的办法,用操作系统的概念来说,一千个

Python开发基础--- 进程间通信、进程池、协程

进程间通信 进程彼此之间互相隔离,要实现进程间通信(IPC),multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的. 进程队列queue 不同于线程queue,进程queue的生成是用multiprocessing模块生成的. 在生成子进程的时候,会将代码拷贝到子进程中执行一遍,及子进程拥有和主进程内容一样的不同的名称空间. 示例1: 1 import multiprocessing 2 def foo(): 3 q.put([11,'hello',True]