精华内容
下载资源
问答
  • python协程系列
    更多相关内容
  • python进阶教程机器学习深度学习长按二维码关注进入正文python协程系列(四)——同步/异步、并发/并行、线程/进程声明:后面会不断穿插这样的一些概念,一定要深入理解一些关键的基本思想。这些基本概念很多的参考资料...
    5a3e964ce7b130c1dde160f52bf0038a.gif

    3e03dd22d399734069a60dfd03e035b6.png

    python进阶教程

    机器学习

    深度学习

    长按二维码关注

    进入正文

    409eeb9e70166baf2ceeb9fc52696fdf.png

    python协程系列(四)——同步/异步、并发/并行、线程/进程

    声明:后面会不断穿插这样的一些概念,一定要深入理解一些关键的基本思想。这些基本概念很多的参考资料参差不齐,讲解不是很清楚,本章将详细,用最通俗易懂的语言解释,什么是线程、进程、同步、异步、阻塞、非阻塞、并发、并行这些很容易弄混的概念,本次的系列文章较长,后续会讲解python协程的实现方式。看完本文,你讲明白一下一些基本的东西:
    (1)并发(并发只是实现异步的手段之一)并不是没有阻塞的,依然有阻塞,相对的分析,并发依然有阻塞。
    (2)怎么理解“事件循环”,那个线程一直在各个方法之间永不停歇的游走,遇到一个yield from 就悬挂起来,然后又走到另外一个方法,依次进行下去,知道事件循环所有的方法执行完毕。
    (3)并发(异步)一定会比同步快吗?当然不是了,参见后面文章的实验。
    (4)并发分为真并发、伪并发,并发与并行的区别在于“是否同时”
    (5)异步是最终的目的,并发和并行都可以实现异步,线程是决定了是使用并发还是并行的手段。
    (6)最好的实现方式当然是并行了,
    首先介绍一些最基本的概念和核心思想。

    目录

    一 进程、线程

       1.1 进程(process)

       1.2 线程(Thread)

       1.3 进程和线程的区别

    二 同步(Sync)和异步(Async)

       2.1 同步

       2.2 异步

       2.3 同步和异步的区别

    三 阻塞和非阻塞

    四 并发并行

    五 关键概念的区分

    六 最终结论概括

       6.1 异步操作的优缺点

       6.2 多线程的优缺点

    01

    进程和线程

       进程(process)    

    进程是Windows系统中的一个基本概念,它包含着一个运行程序所需要的资源。一个正在运行的应用程序在操作系统中被视为一个进程, 进程可以包括一个或多个线程。线程是操作系统分配处理器时间的基本单元,在进程中可以有多个线程同时执行代码。进程之间是相对独立的,一个进程无法访问另一个进程的数据(除非利用分布式计算方式),一个进程运行的失败也不会影响其他进程的运行,Windows系统就是利用进程把工作划分为多个独立的区域的。进程可以理解为一个程序的基本边界。是应用程序的一个运行例程,是应用程序的一次动态执行过程。

      线程(Thread)     

    线程是进程中的基本执行单元,是操作系统分配CPU时间的基本单位,一个进程可以包含若干个线程,在进程入口执行的第一个线程被视为这个进程的主线程。在.NET应用程序中,都是以Main()方法作为入口的,当调用此方法时系统就会自动创建一个主线程。线程主要是由CPU寄存器、调用栈和线程本地存储器(Thread Local Storage,TLS)组成的。CPU寄存器主要记录当前所执行线程的状态,调用栈主要用于维护线程所调用到的内存与数据,TLS主要用于存放线程的状态信息。
    线程的本质:线程不是一个计算机硬件的功能,而是操作系统提供的一种逻辑功能,线程本质上是进程中一段 并发运行的代码,所以线程需要操作系统投入CPU资源来运行和调度。

     进程和线程的区别     

    进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
     1) 简而言之,一个程序至少有一个进程,一个进程至少有一个线程.
     2) 线程的划分尺度小于进程,使得多线程程序的并发性高。
     3) 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
     4) 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
     5) 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。

    02

    同步(Sync)和异步(Async)

      同步    

    所谓同步,就是发出一个功能调用时,在没有得到结果之前,该调用就不返回或继续执行后续操作。

    简单来说,同步就是必须一件一件事做,等前一件做完了才能做下一件事。

      异步     

    异步与同步相对,当一个异步过程调用发出后,调用者在没有得到结果之前,就可以继续执行后续操作。当这个调用完成后, 一般通过状态、通知和回调来通知调用者。对于异步调用,调用的返回并不受调用者控制。

    对于通知调用者的三种方式,具体如下:

    状态
    即监听被调用者的状态(轮询),调用者需要每隔一定时间检查一次,效率会很低。 通知
    当被调用者执行完成后,发出通知告知调用者,无需消耗太多性能。 回调
    与通知类似,当被调用者执行完成后,会调用调用者提供的回调函数。

      同步和异步的区别     

    总结来说,同步和异步的区别:请求发出后,是否需要等待结果,才能继续执行其他操作。

    03

    阻塞和非阻塞

    阻塞和非阻塞这两个概念 仅仅与等待消息通知时的状态有关。跟同步、异步没什么太大关系,也就是说阻塞与非阻塞主要是程序(线程)等待消息通知时的状态角度来说的。
    阻塞和非阻塞关注的是 程序在等待调用结果(消息,返回值)时的状态。
    阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
    非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。 总结:同步执行一般都会有阻塞,但也有可能没阻塞;异步执行也有可能有阻塞,也可能没有阻塞。后面会讲到。

    04

    并发并行

    并发:在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段,再将时间 段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状态。.这种方式我们称之为 并发(Concurrent)。

    并行:当系统有一个以上CPU时,则线程的操作有可能非并发。当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)

    并发和并行的区别:
    (1)你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。因为在完成吃饭这件事情之前,打电话这件事你是完全没开始的,是一个一个来的)
    (2)你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。因为吃饭和电话两件事情都处于启动状态,而不是一件事做完才启动另一件事,但是虽然几件事情都开始了,但因为是一个线程,还是一个一个交替去做的,这也是python协程的思想。
    (3)你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。因为这是同时在进行多件事情,而不是交替执行。
    怎么区别呢?区分它们最关键的点就是:是否是『同时』。并发的关键是你有处理多个任务的能力,不一定要同时;但是并行的关键是你有同时处理多个任务的能力。

    05

    关键概念的区分

    (1)阻塞/非阻塞:关注的是程序在等待调用结果(消息,返回值)时的状态 (2)同步/异步:关注的是消息通知的机制。即等到完全做完才通知,还是你先做你的,我先做我的 ,你做完了再来通知我就可以了。
    所谓同步,就是在发出一个*调用*时,在没有得到结果之前,该*调用*就不返回。但是一旦调用返回,就得到返回值了。
    换句话说,就是由*调用者*主动等待这个*调用*的结果。
    而异步则是相反,*调用*在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在*调用*发出后,*被调用者*通过状态、通知来通知调用者,或通过回调函数处理这个调用。
    上面的两组概念是可以两两搭配的,即 (3)同步阻塞、同步非阻塞,异步阻塞、异步非阻塞。
    举个简单的例子来描述这四种情况,老张要做两件事,用水壶烧开水,看电视,两件事情即两个任务,两个函数。 同步阻塞:老张把水壶放到火上,就坐在那里等水开,开了之后我再去看电视。() 同步非阻塞:老张把水壶放到火上,去客厅看电视,时不时去厨房看看水开没有。(同步非阻塞)
    老张还是觉得自己有点傻,于是变高端了,买了把会响笛的那种水壶。水开之后,能大声发出嘀~~~~的噪音。 异步阻塞:老张把响水壶放到火上,然后就坐在旁边等着听那个烧开的提示音。(异步阻塞)
    异步非阻塞:老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶。(异步非阻塞)
    乍一看,这“同步阻塞、意不阻塞”似乎没有什么区别,但实际上是有区别的,所谓同步异步,指的是 消息通知的机制。区别在哪里呢?
    在这个例子中同步异步只是对于水壶而言。在使用普通水壶的时候,我要自己主动去观察水是不是烧开了,自己主动去获取烧开的这个结果,即所谓的同步;但是在响水壶的时候,我不需要再管水烧到什么程度了,因为只要水烧开了,那个滴滴的噪声就会通知我的,即所谓的异步。
    他们的相同点是,在烧水的过程中,老王啥也没干,即“阻塞”。 (4)四种总结——同步/异步与阻塞/非阻塞 同步阻塞形式:效率是最低的。拿上面的例子来说,在烧水的过程中,什么别的事都不做。 同步非阻塞形式:实际上是效率低下的。因为老王需要不断的在看电视与烧水之间来回跑动,看一下电视,又要去看一下水烧开没有,这样来回跑很多次,在程序中,程序需要在这两种不同的行为之间来回的切换,效率可想而知是低下的。 异步阻塞形式:异步操作是可以被阻塞住的,只不过它不是在处理消息时阻塞,而是在等待消息通知时被阻塞。 这个效率其实跟同步阻塞差不多的。 异步非阻塞形式:效率更高。因为老王把水烧好之后就不用管了,可以安安心心去看电视,不用来回奔波看水烧开了没,因为水烧开了会有提示告诉他水烧好了,这样效率岂不是更高。
    那有没有更好的办法?当然有,如果老王还有一个帮手老张,让老王自己看电视、同时老张去烧开水,这样岂不是更好?这就是所谓的并行。 (4)并发/并行、同步/异步、阻塞/非阻塞 并发/并行:即能够开启多个任务,多个任务交替执行为并发,多个任务同时执行为并行 同步/异步:关注的是消息通知的机制,主动等候消息则为同步、被动听消息则为异步 阻塞/非阻塞:关注的是等候消息的过程中有没有干其他事。 总结:上面的几组概念,时刻穿插的,并没有完全的等价关系,所以经常有人说,异步就是非阻塞,同步就是阻塞,并发就是非阻塞、并行就是非阻塞,这些说法都是不完全准确地。

    06

    最终结论概括

    并发和并行都是实现异步编程的思路,只有一个线程的并发,称之为“伪并发”;有多个线程的并发称之为“真并发”,真并发与并行是很接近的。

    异步操作的优缺点    

    因为异步操作无须额外的线程负担(这里指的是单线程交替执行的“伪并发”),并且使用回调的方式进行处理,在设计良好的情况下,处理函数可以不必使用共享变量(即使无法完全不用,最起码可以减少 共享变量的数量),减少了死锁的可能。当然异步操作也并非完美无暇。编写异步操作的复杂程度较高,程序主要使用回调方式进行处理,与普通人的思维方式有些 初入,而且难以调试。

    多线程的优缺点    

    多线程的优点很明显,线程中的处理程序依然是顺序执行,符合普通人的思维习惯,所以编程简单。但是多线程的缺点也同样明显,线程的使用(滥用)会给系统带来上下文切换的额外负担。并且线程间的共享变量可能造成死锁的出现。异步与多线程,从辩证关系上来看,异步和多线程并不时一个同等关系,(因为单线程也是可以实现异步的)异步是目的,多线程只是我们实现异步的一个手段.什么是异步:异步是当一个调用请求发送给被调用者,而调用者不用等待其结果的返回.实现异步可以采用多线程技术或则交给另外的进程来处理。

    推 荐 阅 读

    python标准库系列教程(五)——unittest单元测试(上篇)

    python标准库系列教程(五)——unittest单元测试(中篇)

    python标准库系列教程(五)——unittest单元测试(下篇)

    python高级调试技巧(一)——原生态的pdb调试

    python标准库系列教程(四)——collections库详细教程python标准库系列教程(三)——operator库详细教程

    python标准库系列教程(二)——functools (下篇)

    python标准库系列教程(二)——functools (中篇)

    python标准库系列教程(二)——functools (上篇)

    python标准库系列教程(一)——itertools

    e1bea20e3a6709b06e8f6985a6e94e4e.png

    赶紧关注我们吧

    您的点赞和分享是我们进步的动力!

    ↘↘↘

    展开全文
  • python进阶教程机器学习深度学习长按二维码关注进入正文Python协程系列(一)——生成器generator以及yield表达式详解声明:本文将详细讲解python协...
    640?wx_fmt=gif

    640?wx_fmt=jpeg

    python进阶教程

    机器学习

    深度学习

    长按二维码关注

    进入正文


    640?wx_fmt=png

    Python协程系列(一)——生成器generator以及yield表达式详解

    声明:本文将详细讲解python协程的实现机理,为了彻底的弄明白它到底是怎么一回事,鉴于篇幅较长,将彻底从最简单的yield说起从最简单的生成器开始说起,因为很多看到这样一句话的时候很懵,即“yield也是一种简单的协程”,这到底是为什么呢?本次系列文章“ python协程系列文章”将从最简单的生成器、yield、yield from说起,然后详细讲解asyncio的实现方式。本文主要讲解 Python的生成器的各种详细操作,以及 yield表达式的详细应用。

    目录

    生成器generator以及yield表达式详解

    1、最简单的生成器

    2、send()方法的使用

    3、yield语句的用法总结

    4、迭代器(生成器)的send方法详解

              ——主要目的是“交互”

    5、生成器throw的方法用法

    6、生成器的启动与关闭close

        6.1 生成器的启动

        6.2 生成器的关闭——通过close()方法

    7、生成器的终止迭代——StopIteration

    注意:关于什么是可迭代对象、什么是生成器、什么是迭代器这里不再赘述。
    yield是实现生成器的重要关键字,但是yield语句有一些非常重要的小细节需要注意,可能我们在写一个简单的生成器的时候有很多东西没有用到,这里将分情况逐一讨论。特别是生成器的三个重要方法, 一个是next()、一个是send(),一个是throw(),他们到底有什么样的作用。

    01

    最简单的生成器

    def my_generator(n):
        for i in range(n):
            yield i
    

    02

    send()方法的使用

    def my_generator(n):
        for i in range(n):
            temp=yield i
            print(f'我是{temp}')
    
    g=my_generator(5)
    
    print(next(g)) #输出0
    print(next(g)) #输出1
    g.send(100)    #本来输出2,但是传入新的值100,改为输出100
    print(next(g)) #输出3
    print(next(g)) #输出4
    
    '''
    0         #第一次迭代
    ---------------------
    我是None
    1         #第二次迭代
    ---------------------
    我是100   #第三次迭代
    ---------------------
    我是None  #第四次迭代
    3
    ---------------------
    我是None  #第五次迭代
    4
    '''
    

    从上面可以看出yield语句与普通函数的return语句的区别在哪里了,主要集中在以下几点(1)return 不能写成“temp=return xxxx”的形式,会提示语法错误,但是yield可以写成“temp=yield xxxx”的形式;(2)普通函数return后面的语句都是不会再执行的,但是yield语句后面的依然会执行,但是需要注意的是,由于“延迟加载”特性,yield后面的代码并不是在第一次迭代的时候执行的,而是第二次迭代的时候才执行第一次yield后面没有执行的代码。也正是这个特性,构成了yield为什么是实现协程的最简单实现。(3)使用send()方法传进去的值,实际上就是yield表达式返回的值,这就是为什么前面每次输出print(temp)都打印出None,因为没有send值,所以temp为None,但是send(100)之后却打印100,因为此时temp就是100了。

    def my_generator(n):
        for i in range(n):
            temp = yield   #yield后面没有任何东西,这时候无法迭代值,每次都是None
            print(f'我是{temp}')
    
    g=my_generator(5)
    
    print(next(g))
    print(next(g))
    g.send(100)
    print(next(g))
    print(next(g))
    '''
    None
    -----------------------
    我是None
    None
    -----------------------
    我是100   #这个100并不是迭代的100哦,而是send进去100,实际上是temp的打印值
    -----------------------
    我是None
    None
    -----------------------
    我是None
    None
    '''
    

    03

    yield语句的用法总结

    yield的一般形式为:

    temp=yield 表达式(每次迭代要返回的值)
    (1)如果要返回确定的值,后面的表达式不可省略,绝大部分情况下我们也不省略,否则只能返回None;
    (2)如果使用了send(value),传递进去的那个value回取代那个表达式的值,并且会将传递进去的那个值返回给yield表达式的结果temp,所以如果想在yield后面使用传递进去的那个值,必须要有使用temp,否则无法使用;
    (3) yield语句的一般形式
             temp=yield expression  (推荐:既可以返回迭代的值,也可以接受send进去的参数并使用)

             yield expression(也可以使用:)

             temp=yield          (不推荐)

             yield                   (不推荐)

    04

    迭代器(生成器)的send方法详解


    查看send的定义,得到send(arg)是有返回值的,而且他的返回值就是原本我应该迭代出来的那个值,如下所示:
    def my_generator(n):
        for i in range(n):
            yield i
    
    g=my_generator(5)
    
    print(next(g))
    print(next(g))
    g.send(100)
    print(next(g))
    print(next(g))
    
    '''
    0
    1
    3
    4
    '''
    #我们发现虽然100传进去了,但是他并没有迭代出来,那原来的2去哪里了呢?send(100)实际上就是返回的2
    
    如果改为以下代码:
    print(next(g))
    print(next(g))
    a=g.send(100)
    print('我是{0}'.format(a))
    print(next(g))
    print(next(g))
    '''
    0
    1
    我是2
    3
    4
    '''
    
    send(arg)方法总结:

    (1)它的主要作用是,当我需要手动更改生成器里面的某一个值并且使用它,则send发送进去一个数据,然后保存到yield语句的返回值,以提供使用
    (2)send(arg)的返回值就是那个本来应该被迭代出来的那个值。 这样既可以保证我能够传入新的值,原来的值也不会弄丢

    05

    生成器throw的方法用法


    这个函数相比较于前面的next()、send()来说更加复杂,先看一下它的函数描述:

    raise exception in generator,return next yielded value or StopIteration,即在生成器中抛出异常,并且这个throw函数会返回下一个要迭代的值或者是StopIteration。还是通过几个例子来看吧!
    def my_generator():
        yield 'a'
        yield 'b'
        yield 'c'
    g=my_generator()
    print(next(g))
    print(next(g))
    print('-------------------------')
    print(g.throw(StopIteration))
    print(next(g))
    '''运行结果为:
    a
    b
    -------------------------
    StopIteration
    '''
    

    因为在迭代完 b 之后,就触发了StopIteration异常,这相当于后面的 ‘c’ 已经没用了,跳过了c ,c再也不会执行,就中断了,所以后面的 'c'再也不会迭代,所以这里不会再返回任何值,返回的是StopIteration。

    再看一个例子:

    def my_generator():
        try:
            yield 'a'
            yield 'b'
            yield 'c'
            yield 'd'
            yield 'e'
        except ValueError:
            print('触发“ValueError"了')
        except TypeError:
            print('触发“TypeError"了')
    
    g=my_generator()
    print(next(g))
    print(next(g))
    print('-------------------------')
    print(g.throw(ValueError))
    print('-------------------------')
    print(next(g))
    print(next(g))
    print('-------------------------')
    print(g.throw(TypeError))
    print('-------------------------')
    print(next(g))
    '''运行结果为:
    a
    b
    -------------------------
    触发“ValueError"了
    StopIteration
    '''
    

    当前面两次执行了a和b之后,向生成器扔进去一个异常,触发ValueError异常,这时候意味着try后面的c、d、e已经作废了,不会再有用,这个生成器已经终止了,因此g.throw()会返回StopIteration。

    再看一个例子:

    def my_generator():
        while True:
            try:
                yield 'a'
                yield 'b'
                yield 'c'
                yield 'd'
                yield 'e'
            except ValueError:
                print('触发“ValueError"了')
            except TypeError:
                print('触发“TypeError"了')
    
    g=my_generator()
    print(next(g))
    print(next(g))
    print('-------------------------')
    print(g.throw(ValueError))
    print('-------------------------')
    print(next(g))
    print(next(g))
    print('-------------------------')
    print(g.throw(TypeError))
    print('-------------------------')
    print(next(g))
    '''运行结果为:
    a
    b
    -------------------------
    触发“ValueError"了
    a
    -------------------------
    b
    c
    -------------------------
    触发“TypeError"了
    a
    -------------------------
    b
    '''
    
    解释:
    出现这样的结果是不是很意外?它和上面的那个例子只有一个while只差,为什么结果差这么多,解释如下:
    首先print(next(g))两次:会输出a、b,并停留在c之前。
    然后由于执行了g.throw(ValueError),所以会跳过所有后续的try语句,也就是说yield 'c'、yield 'd'、yield 'e'不会被执行,然后进入到except语句,打印出  触发“ValueError"了。然后再次进入到while语句部分,消耗一个yield,此时因为是重新进入的while,小号的依然是第一个yield 'a',所以会输出a。实际上这里的a也就是g.throw()的返回值,因为它返回的是下一个迭代的数;
    然后在print(next(g))两次,会执行yield b’、yield 'c’语句,打印出b、c,并停留在执行完该语句后的位置,即yield 'd'之前。

    然后再g.throw(TypeError):会跳出try语句,从而后面的d,e不会被执行,下次自一次进入while,依然打印出a。

    最后,执行了一次print(next(g)),打印出b。


    06

    生成器的启动与关闭close


    生成器的启动与关闭close
    6.1 生成器的启动


    使用close()方法手动关闭生成器函数,后面的调用会直接返回StopIteration异常
    这里所讨论的启动不是使用for循环迭代,我们在使用for循环迭代的时候可能没有去考虑“启动”与“关闭”这些事情,这里指的是使用next()内置方法一个一个迭代的情形。在第一次迭代的时候,一定要先启动生成器,启动的两种方法为:
    第一:直接使用next(g),这会直接开始迭代第一个元素(推荐使用这个启动)
    第二:使用g.send(None)进行启动,注意第一次启动的时候只能传入None,如果传入其他具体的指则会报错哦!
    def my_generator():
        yield 1
        yield 2
        yield 3
        yield 4
    
    g = my_generator()
    g.send(None)   #第一次启动,本来第一次应该迭代的1,这里被取代了,但是send(None)会返回1
    print(next(g))
    print(next(g))
    print(next(g))
    
    '''运行结果为:
    2
    3
    4
    '''
    


    生成器的启动与关闭close
    6.2 生成器的关闭


    如果一个生成器被中途关闭之后,在此调用next()方法,则会显示错误,如下:
    def my_generator():
        yield 1
        yield 2
        yield 3
        yield 4
    
    g = my_generator()
    print(next(g))
    print(next(g))
    g.close()
    print(next(g))   #在此处会显示错误
    print(next(g))
    '''运行结果为:
    1
    2
    显示StopIteration
    '''
    

    07

    生成器的终止迭代——StopIteration


    前面讲的手动关闭生成器,使用close()方法,后面的迭代或抛出StopIteration异常。另外

    在一个生成器中,如果没有return,则默认执行到函数完毕时返回StopIteration;

    def g1():
        yield 1
    g=g1()
    next(g)    #第一次调用next(g)时,会在执行完yield语句后挂起,所以此时程序并没有执行结束。
    next(g)    #程序试图从yield语句的下一条语句开始执行,发现已经到了结尾,所以抛出StopIteration异常。
    '''运行结果为:
    1
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    StopIteration
    '''
    
    如果遇到return,如果在执行过程中 return,则直接抛出 StopIteration 终止迭代。
    def g2():
        yield 'a'
        return
        yield 'b'
    g=g2()
    next(g)    #程序停留在执行完yield 'a'语句后的位置。
    next(g)    #程序发现下一条语句是return,所以抛出StopIteration异常,这样yield 'b'语句永远也不会执行。
    '''运行结果为:
    a
    b
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    StopIteration
    '''
    
    如果在return后返回一个值,那么这个值为StopIteration异常的说明,不是程序的返回值。
    def g3():
        yield 'a'
        return '这是错误说明'
        yield 'b'   #有一些编辑器会提示错误,此处为unreachable code,即不可到达的代码
    g=g3()
    next(g)
    next(g)
    '''运行结果为:
    a
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    StopIteration: 这是错误说明
    '''
    
    注意: 生成器没有办法使用return来返回值。因为return返回的那个值是通过StopIteration的异常信息返回的,所以我没办法直接获取这个return返回的值。
    当然上面所说的无法获取return返回值,我们指的是没有办法通过result=g3()这种形式获取return的返回值。实际上还是有相依欧诺个的手段获取这个return的值的,有两种方法:
    方法一:使用后面的yield from 语句(下文在讲解)
    方法二: 因为return返回的值是作为StopIteration的一个value属性存在的,StopIteration本质上是一个类,所以可以通过访问它的value属性获取这个return返回的值。使用下面的代码:
    def g3():
        yield 'a'
        return '这是错误说明'
        yield 'b'
    g=g3()
    
    try:   
        print(next(g))  #a
        print(next(g))  #触发异常
    except StopIteration as exc:
        result=exc.value
        print(result)
    '''运行结果为:
    a
    这是错误说明
    '''
    
    总结:上面详细讲解了关于python生成器的各个方法的详细使用情况,还没有正式进入到协程的部分,关于协程的详细讲解将会在后续的系列文章中逐渐讲解,有兴趣的小伙伴可以关注一下。

    640?wx_fmt=png

    2019/01/14

    Saturday

    小伙伴们,python的yield和生成器是不是掌握熟练了呢?看完这篇文章你一定会有不一样的收获的,后面还有系列文章连载,请记得关注哦!如果你有需要,就添加我的公众号哦,里面分享有海量资源,包含各类数据、教程等,后面会有更多面经、资料、数据集等各类干货等着大家哦,重要的是全都是免费、无套路分享,有兴趣的小伙伴请持续关注!

    推 荐 阅 读


    640?wx_fmt=jpeg

    赶紧关注我们吧

    的点赞和分享是我们进步的动力!

    ↘↘↘

    展开全文
  • python进阶教程机器学习深度学习长按二维码关注进入正文python协程系列(四)——同步/异步、并发/并行、线程/进程声明:后面会不断穿插这样的一些概念,一定要深入理...
    640?wx_fmt=gif

    640?wx_fmt=jpeg

    python进阶教程

    机器学习

    深度学习

    长按二维码关注

    进入正文


    640?wx_fmt=png

    python协程系列(四)——同步/异步、并发/并行、线程/进程

    声明:后面会不断穿插这样的一些概念,一定要深入理解一些关键的基本思想。这些基本概念很多的参考资料参差不齐,讲解不是很清楚,本章将详细,用最通俗易懂的语言解释,什么是线程、进程、同步、异步、阻塞、非阻塞、并发、并行这些很容易弄混的概念,本次的系列文章较长,后续会讲解python协程的实现方式。看完本文,你讲明白一下一些基本的东西:“是否同时”基本的概念和核心思想。


    目录

    一 进程、线程

       1.1 进程(process)

       1.2 线程(Thread)

       1.3 进程和线程的区别

    二 同步(Sync)和异步(Async)

       2.1 同步

       2.2 异步

       2.3 同步和异步的区别

    三 阻塞和非阻塞

    四 并发并行

    五 关键概念的区分

    六 最终结论概括

       6.1 异步操作的优缺点

       6.2 多线程的优缺点

    01

    进程和线程

       进程(process)    

    进程是Windows系统中的一个基本概念,它包含着一个运行程序所需要的资源。一个正在运行的应用程序在操作系统中被视为一个进程, 进程可以包括一个或多个线程。线程是操作系统分配处理器时间的基本单元,在进程中可以有多个线程同时执行代码。进程之间是相对独立的,一个进程无法访问另一个进程的数据(除非利用分布式计算方式),一个进程运行的失败也不会影响其他进程的运行,Windows系统就是利用进程把工作划分为多个独立的区域的。进程可以理解为一个程序的基本边界。是应用程序的一个运行例程,是应用程序的一次动态执行过程。

      线程(Thread)     

    线程是进程中的基本执行单元,是操作系统分配CPU时间的基本单位,一个进程可以包含若干个线程,在进程入口执行的第一个线程被视为这个进程的主线程。在.NET应用程序中,都是以Main()方法作为入口的,当调用此方法时系统就会自动创建一个主线程。线程主要是由CPU寄存器、调用栈和线程本地存储器(Thread Local Storage,TLS)组成的。CPU寄存器主要记录当前所执行线程的状态,调用栈主要用于维护线程所调用到的内存与数据,TLS主要用于存放线程的状态信息。
    线程的本质:线程不是一个计算机硬件的功能,而是操作系统提供的一种逻辑功能,线程本质上是进程中一段 并发运行的代码,所以线程需要操作系统投入CPU资源来运行和调度。

     进程和线程的区别     

    进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

     1) 简而言之,一个程序至少有一个进程,一个进程至少有一个线程.
     2) 线程的划分尺度小于进程,使得多线程程序的并发性高。
     3) 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
     4) 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
     5) 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。

    02

    同步(Sync)和异步(Async)

      同步    

    所谓同步,就是发出一个功能调用时,在没有得到结果之前,该调用就不返回或继续执行后续操作。

    简单来说,同步就是必须一件一件事做,等前一件做完了才能做下一件事。


      异步     

    异步与同步相对,当一个异步过程调用发出后,调用者在没有得到结果之前,就可以继续执行后续操作。当这个调用完成后, 一般通过状态、通知和回调来通知调用者。对于异步调用,调用的返回并不受调用者控制。

    对于通知调用者的三种方式,具体如下:

    状态
    即监听被调用者的状态(轮询),调用者需要每隔一定时间检查一次,效率会很低。
    通知
    当被调用者执行完成后,发出通知告知调用者,无需消耗太多性能。
    回调
    与通知类似,当被调用者执行完成后,会调用调用者提供的回调函数。

      同步和异步的区别     

    总结来说,同步和异步的区别:请求发出后,是否需要等待结果,才能继续执行其他操作。

    03

    阻塞和非阻塞


    阻塞和非阻塞这两个概念 仅仅与等待消息通知时的状态有关。跟同步、异步没什么太大关系,也就是说阻塞与非阻塞主要是程序(线程)等待消息通知时的状态角度来说的。

    阻塞和非阻塞关注的是 程序在等待调用结果(消息,返回值)时的状态。

    阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。

    非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。

    总结:同步执行一般都会有阻塞,但也有可能没阻塞;异步执行也有可能有阻塞,也可能没有阻塞。后面会讲到。

    04

    并发并行


    并发:在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段,再将时间 段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状态。.这种方式我们称之为 并发(Concurrent)。

    并行:当系统有一个以上CPU时,则线程的操作有可能非并发。当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)

    并发和并行的区别:『同时』。并发的关键是你有处理多个任务的能力,不一定要同时;但是并行的关键是你有同时处理多个任务的能力。


    05

    关键概念的区分


    (1)阻塞/非阻塞:关注的是程序在等待调用结果(消息,返回值)时的状态

    (2)同步/异步:关注的是消息通知的机制。即等到完全做完才通知,还是你先做你的,我先做我的 ,你做完了再来通知我就可以了。
    所谓同步,就是在发出一个*调用*时,在没有得到结果之前,该*调用*就不返回。但是一旦调用返回,就得到返回值了。
    换句话说,就是由*调用者*主动等待这个*调用*的结果。
    而异步则是相反,*调用*在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在*调用*发出后,*被调用者*通过状态、通知来通知调用者,或通过回调函数处理这个调用。
    上面的两组概念是可以两两搭配的,即

    (3)同步阻塞、同步非阻塞,异步阻塞、异步非阻塞。
    举个简单的例子来描述这四种情况,老张要做两件事,用水壶烧开水,看电视,两件事情即两个任务,两个函数。
    同步阻塞:老张把水壶放到火上,就坐在那里等水开,开了之后我再去看电视。()
    同步非阻塞:老张把水壶放到火上,去客厅看电视,时不时去厨房看看水开没有。(同步非阻塞)
    老张还是觉得自己有点傻,于是变高端了,买了把会响笛的那种水壶。水开之后,能大声发出嘀~~~~的噪音。
    异步阻塞:老张把响水壶放到火上,然后就坐在旁边等着听那个烧开的提示音。(异步阻塞)
    异步非阻塞:老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶。(异步非阻塞)
    乍一看,这“同步阻塞、意不阻塞”似乎没有什么区别,但实际上是有区别的,所谓同步异步,指的是 消息通知的机制。区别在哪里呢?
    在这个例子中同步异步只是对于水壶而言。在使用普通水壶的时候,我要自己主动去观察水是不是烧开了,自己主动去获取烧开的这个结果,即所谓的同步;但是在响水壶的时候,我不需要再管水烧到什么程度了,因为只要水烧开了,那个滴滴的噪声就会通知我的,即所谓的异步。
    他们的相同点是,在烧水的过程中,老王啥也没干,即“阻塞”。

    (4)四种总结——同步/异步与阻塞/非阻塞

    同步阻塞形式:效率是最低的。拿上面的例子来说,在烧水的过程中,什么别的事都不做。
    同步非阻塞形式:实际上是效率低下的。因为老王需要不断的在看电视与烧水之间来回跑动,看一下电视,又要去看一下水烧开没有,这样来回跑很多次,在程序中,程序需要在这两种不同的行为之间来回的切换,效率可想而知是低下的。
    异步阻塞形式:异步操作是可以被阻塞住的,只不过它不是在处理消息时阻塞,而是在等待消息通知时被阻塞。 这个效率其实跟同步阻塞差不多的。
    异步非阻塞形式:效率更高。因为老王把水烧好之后就不用管了,可以安安心心去看电视,不用来回奔波看水烧开了没,因为水烧开了会有提示告诉他水烧好了,这样效率岂不是更高。

    那有没有更好的办法?当然有,如果老王还有一个帮手老张,让老王自己看电视、同时老张去烧开水,这样岂不是更好?这就是所谓的并行。

    (4)并发/并行、同步/异步、阻塞/非阻塞

    并发/并行:即能够开启多个任务,多个任务交替执行为并发,多个任务同时执行为并行
    同步/异步:关注的是消息通知的机制,主动等候消息则为同步、被动听消息则为异步
    阻塞/非阻塞:关注的是等候消息的过程中有没有干其他事。

    总结:上面的几组概念,时刻穿插的,并没有完全的等价关系,所以经常有人说,异步就是非阻塞,同步就是阻塞,并发就是非阻塞、并行就是非阻塞,这些说法都是不完全准确地。

    06

    最终结论概括


    并发和并行都是实现异步编程的思路,只有一个线程的并发,称之为“伪并发”;有多个线程的并发称之为“真并发”,真并发与并行是很接近的。

    异步操作的优缺点    

    因为异步操作无须额外的线程负担(这里指的是单线程交替执行的“伪并发”),并且使用回调的方式进行处理,在设计良好的情况下,处理函数可以不必使用共享变量(即使无法完全不用,最起码可以减少 共享变量的数量),减少了死锁的可能。当然异步操作也并非完美无暇。编写异步操作的复杂程度较高,程序主要使用回调方式进行处理,与普通人的思维方式有些 初入,而且难以调试。

    多线程的优缺点    

    多线程的优点很明显,线程中的处理程序依然是顺序执行,符合普通人的思维习惯,所以编程简单。但是多线程的缺点也同样明显,线程的使用(滥用)会给系统带来上下文切换的额外负担。并且线程间的共享变量可能造成死锁的出现。

    异步与多线程,从辩证关系上来看,异步和多线程并不时一个同等关系,(因为单线程也是可以实现异步的)异步是目的,多线程只是我们实现异步的一个手段.什么是异步:异步是当一个调用请求发送给被调用者,而调用者不用等待其结果的返回.实现异步可以采用多线程技术或则交给另外的进程来处理。

    推 荐 阅 读

    640?wx_fmt=jpeg

    赶紧关注我们吧

    您的点赞和分享是我们进步的动力!

    ↘↘↘

    展开全文
  • python协程系列(三)——yield from原理详解

    万次阅读 多人点赞 2018-12-26 14:45:28
    声明:本文将详细讲解python协程的实现机理,为了彻底的弄明白它到底是怎么一回...本次系列文章“python协程系列文章”将从最简单的生成器、yield、yield from说起,然后详细讲解asyncio的实现方式。本文主要讲解什...
  • python协程系列

    2019-05-07 09:22:00
    声明:本文针对的是python3.4以后的版本的,因为从3.4开始才引入asyncio,后面的3.5 3.6 3.7版本是向前兼容的,只不过语法上面有稍微的改变。比如在3.4版本中使用@asyncio.coroutine装饰器和yield from语句,但是在...
  • 声明:本文将详细讲解python协程的实现机理,为了彻底的弄明白它到底是怎么一回...本次系列文章“python协程系列文章”将从最简单的生成器、yield、yield from说起,然后详细讲解asyncio的实现方式。本文主要...
  • 上一篇python协程1:yield的使用介绍了: 生成器作为协程使用时的行为和状态 使用装饰器预激协程 调用方如何使用生成器对象的 .throw(…) 和 .close() 方法控制协程 这一篇将介绍: 协程终止时如何返回值
  • python协程(超详细)

    千次阅读 2021-12-21 16:56:38
    # Python 中的迭代 for value in [2, 3, 4]: print(value) 1.2 可迭代对象 标准概念:在类里面定义__iter__方法,并使用该类创建的对象就是可迭代对象 简单记忆:使用for循环遍历取值的对象叫做可迭代对象, 比如...
  • 本文的文字及图片来源于网络,仅供学习、...协程的底层架构是在pep342 中定义,并在python2.5 实现的。 python2.5 中,yield关键字可以在表达式中使用,而且生成器API中增加了 .send(value)方法。生成器可以使用.send(…
  • 声明:本文针对的是python3.4以后的版本的,因为从3.4开始才引入asyncio,后面的3.5 3.6 3.7版本是向前兼容的,只不过语法上面有稍微的改变。比如在3.4版本中使用@asyncio.coroutine装饰器和yield from语句,但是在...
  • 前两篇我们已经介绍了python 协程的使用和yield from 的原理,这一篇,我们用一个例子来揭示如何使用协程在单线程中管理并发活动。 什么是离散事件仿真 Wiki上的定义是: 离散事件仿真将系统随时间的变化抽象成一...
  • python进阶教程机器学习深度学习长按二维码关注进入正文python协程系列(二)——python协程的通俗理解以及使用yield关键字实现协程声明:本文将详细讲解py...
  • 讲解不是很清楚,本章将详细,用最通俗易懂的语言解释,什么是线程、进程、同步、异步、阻塞、非阻塞、并发、并行这些很容易弄混的概念,本次的系列文章较长,后续会讲解python协程的实现方式。看完本文,你讲明白...
  • 声明:本文将详细讲解python协程的实现机理,为了彻底的弄明白它到底是怎么一回事...本次系列文章“python协程系列文章”将从最简单的生成器、yield、yield from说起,然后详细讲解asyncio的实现方式。本文主要讲解P...
  • (1)运行异步协程asyncio.run(coro, *, debug=False) #运行一个一步程序,参见上面(2)创建任务task=asyncio.create_task(coro) #python3.7 ,参见上面task = asyncio.ensure_future(coro())(3)睡眠await asyncio.sleep...
  • 声明:python协程系列文章的上一篇,即第五篇,详细介绍了asyncio的核心概念,asyncio的设计架构,Task类的详细作用,本文为系列文章的第六篇,将介绍更加底层的API,以EventLoop和Future为主,介绍他们的设计理念,...
  • python进阶教程机器学习深度学习长按二维码关注进入正文python协程系列(三)——yield from详解声明:本文将详细讲解python协程的实现机理,为了彻底的...
  • 本文实例讲述了python协程用法。分享给大家供大家参考。具体如下: 把函数编写为一个任务,从而能处理发送给他的一系列输入,这种函数称为协程 def print_matchs(matchtext): print looking for,matchtext while ...
  • python进阶教程机器学习深度学习长按二维码关注进入正文python协程系列(六)——asyncio的EvenrLoop以及Future详解声明:python协程系列文...
  • python - 协程(一)

    2018-07-04 22:24:33
    协程 迭代器 可迭代(Iterable): 直接作用于For循环的变量”, 迭代器():不但可以作用于for循环,还可以被next调用”, list是一个典型的可以迭代对象,但不是迭代器”, 通过isinstance判断”, iterable 和 ...
  • 声明:python协程系列文章的上一篇,即第六篇,详细介绍了asyncio的几个底层API概念,asyncio的事件循环EventLoop,Future类的详细使用,以及集中回答了关于异步编程的一些疑问,本文为系列文章的第七篇,将介绍如何...
  • python进阶教程机器学习深度学习长按二维码关注进入正文python协程系列(七)——asyncio结合多线程解决阻塞问题以及timer模拟声明:python协程系列文...
  • 前面,我们从大的结构上认识了最新的Python 3.7里面的asyncio标准库。接下来,我们就开始一点一点的来学习asyncio的使用。一、安装 Python 3.7我的系统是 Ubuntu 16.04,里面有 Python 2.7 和 Python 3.6。2.7是系统...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 5,387
精华内容 2,154
关键字:

python协程系列