阻塞和非阻塞,异步和同步,以及python协程

暂时踢走这个奇葩

阻塞和非阻塞,异步和同步。经常笛卡尔积。组合成4个概念。其中单个理解起来,脑袋就要炸了,更何况四个组合根本无法理解。没事,这篇文章,我敢肯定会教给你。首先第一步,踢出阻塞异步这个奇葩。我们只看,阻塞同步,非阻塞同步,非阻塞异步。

进程

我们写代码,多简单,一些命令,一点运行,结果出来了。但是操作系统可没闲着。我们写的程序只是文本。如何让电脑运行。电脑会给他分配资源,cpu,内存。并给他分配时间。执行它的逻辑。我们的代码只是文本。操作系统执行它的过程叫进程。

线程

进程会直接去全部的操作系统资源里取。线程可以理解为小进程。它是进程底下的。进程从操作系统那获得了资源。线程又从进程处分配它获得的资源。但本质上。线程也是操作系统执行它的一切相关过程。

主线程

这个线程是个相对概念。不是一个特殊线程。一个线程在执行一个逻辑。只要它开小差去执行其它的了。我们就相对的说它原本的是主线程。

阻塞和非阻塞

这些概念都是对于线程来说的。当有其它任务时,主线程无法在执行它原本的任务,就叫阻塞。线程还可以就叫非阻塞。还记得线程吗,阻塞意味着,操作系统的全部调度去执行其它任务,而不原本。代码想要运行必须由操作系统去在底层真正的执行它。

同步和异步

暂时抛开线程。这些概念也是指代码是否还可以执行原本的逻辑。比如说 一个代码的任务就负责输出1


def download():
    for i in range(10):
        print("下载")

i=0
for i in range(100):
    print(1)
    i+=1
    if i==10:
        download()

在这里插入图片描述
可以看到系统无法去执行它原来的任务。必须输出完下载才可以继续输出它的1.此时就叫同步。如果下载时不影响1的输出就叫异步。

# 两个线程一个线程输出1,一个线程输出下载中
import threading


def print_num():
    for i in range(1, 11):
        print(1)

def print_download():
    for i in range(1, 11):
        print("下载中")

t=threading.Thread(target=print_download)
t.start()
print_num()

在这里插入图片描述
异步,主线程输出1,下载根本不影响1的输出。

正文开始

如果我们踢出阻塞异步这个奇葩。你会发现这些逻辑想吃饭一样简单。
当一个只有一个线程时,切换任务,意味着操作系统的调度换执行逻辑了。比如操作系统正在从内存获取1输出。当切换任务,你只有一个线程,它只能从内存里找下载中。不可能再去执行任何关于1的任何底层逻辑。此时线程阻塞,就意味着同步,阻塞同步。(注意 这个结论是在我们不考虑阻塞异步的情况下,注意,注意,注意)
而我们想实现异步怎么办呢。只能通过多个线程(没有阻塞异步)。很简单的逻辑。线程只能执行一个任务。那我执行其它任务时,我换个线程,你原来的线程该干嘛就干嘛不就行了。这就叫非阻塞异步。

这个代码演示上面已经有了。1和下载中的输出。同步我们只用主线程。线程做其它事情,我们相对来说它原来做的任务叫主线程,主线程阻塞了。而异步,我们用了多线程,主线程没阻塞,逻辑上也实现了异步。

那么非阻塞同步呢。我们讲了同步和异步只是逻辑上的代码执行顺序。虽然主线程没被阻塞。但我可以代码逻辑,这个任务必须等其它任务执行完才能运行。此时就像同步一样,原任务无法执行了,可以理解为主线程可以执行,只是业务逻辑上不运行。

t.start()
print_num()
t.join()
print("下载完成")

在这里插入图片描述
这是上面的多线程代码加上join

上面多线程代码,我么已经演示过了,主线程压根没被阻塞。我们输出1很正常,没任何问题。但是代码逻辑上。print(“下载完成”)必须等任务完成才会执行主线程。

精彩的地方快来了

好了重点来了。我们真想逻辑就这样清晰下去。但是抱歉。有阻塞异步。我们逃避了那么久。还是要面对。奇怪了,奇怪了。你不是说。代码执行新任务线程无法继续原先的任务,线程是操作系统执行代码的过程,它只能执行一个吗。原线程无法继续,被阻塞了。哪来的异步。怎么可能逻辑上又可以同时运行。别慌,马上告诉你,如何实现。

并行和并发

我们的电脑可以同时跑多个进程,多个线程。但是同时跑,真的是在同时跑吗?
并行是多核cpu或多台机器一起工作,每个cpu核或机器都可以运行进程,所以是真的同时运行。但是比如我的电脑算高配了。24个核,难道我的电脑只能跑24个进程。(题外话,这也是为啥要分布式。这是真正的并行)。哪台电脑没几百个进程。
在这里插入图片描述
这是怎么回事呢。这样的同样运行其实是操作系统的小把戏,它会根据调度算法,让进程交替运行。它运行一个时间,另个再运行点时间。只要换得够快,就实现了同时运行的效果。这叫并发。所以多线程,我们看起来一起运行其实是交替运行。那么阻塞异步。我们想想可不可以用一个线程也模拟这个过程呢? 真是个挑战。你只有一个线程。我们讲了如果给你另个线程。我们就叫非阻塞了。

真是个巨大的挑战

为什么是挑战呢,因为单线程里的代码,都是贪心鬼,让我执行就必须执行完

在这里插入图片描述

还是这个例子,你只要敢让下载任务开始,它必须执行完。看起来我们很难实现它执行一会,主线程执行会交替执行的效果。

最精彩的地方终于来了

相信我,真的很精彩。你会感叹程序员大佬的厉害,一个和并发毫不相干的东西,居然会有一天在并发上,大放光彩。

生成器

我们在处理数据时可能会遇到特别长的数据,全部读入你内存是不可能的任务,我们可以动态的生成它,每用一个生成一个。
比如

# 请注意,这个bigdata我们使用列表来保存,实际上,真实的情况应该是,这些数据应该是在文件中。并且很大比如50gb
bigdata = list(range(10))

class readlines:
    def __init__(self, bigdata):
        self.bigdata = bigdata
        self.pos = -1

    def __enter__(self):
        return self.read_lines_generator()

    def __exit__(self, exc_type, exc_val, exc_tb):
        pass

    def read_lines_generator(self):
        while self.pos < len(self.bigdata)-1:
            self.pos += 1
            yield self.bigdata[self.pos]


with readlines(bigdata) as r:
    while True:
        try:
            print(next(r))
        except StopIteration:
            break

需要说明一下。我们定义了个列表,来模拟一个超级大的文件。我们的列表会加载到内存中。但不要在意,我们的重点时理解一个新家伙,yield。明白它的执行逻辑。而且这个代码已经抽象出来了大文件的处理方法。

我们不会把大文件全部读到内存中,而是维护self.pos = -1,当前文件的位置。你要时我给你一行,你再要,我就再给你一行。
在这里插入图片描述
而这个一行一行读的实现就靠的就是yield。我们第一次调用next()。代码运行到yield处,产出值后它会暂停,不会继续下次循环。直到你下次调用next() 才会去继续下一次循环去生成新的值,所以叫生成器。

看看你有没有大佬的潜质,奇妙的地方来了,想到了什么。

协程

哈哈哈 我们讲生成器,阻塞了一下我们今天的主要任务阻塞异步。但该收回来了,希望不要忘记我们的逻辑。我们想要用单线程模拟多线程的交替运行。但发现,任务都是贪婪的,让他运行,它必须运行完。我们不想,我们不想让他运行完,让它和主线程交替运行。等一下,不运行完,不运行完。yield 不正是这样嘛

没错就是靠这个,我们实现了,用单线程模拟了两个任务的交替运行,然后yield从生成器,步入了并发编程这个战场。

import time


def download():
    while  True:
        try:
            url = yield
            print("downloading")
            time.sleep(1)
        except GeneratorExit:
            print("GeneratorExit")
            break
        except Exception as e:
            print("Exception", e)

d=download()
for i in range(10):
    print("主线程")
    next(d)

他们开始交替运行了。

在这里插入图片描述

并发,并发,一个生成器用的东西,可以并发编程怎么能不让人惊叹,这部分用最精彩的地方,完全不夸张。 有yield 的函数叫协程

收尾和一点小抱歉

不要忘记我们今天的主题,是异步和同步,阻塞和非阻塞之间的组合。我们认识
到了,代码逻辑上的能否一起运行和线程之间关系。当没有阻塞异步时,这些组合特别容易理解。我们单线程如果它去执行其它任务了,这个线程就不能执行原来的任务,线程阻塞。逻辑上同步这叫阻塞同步。 我们想异步。就多线程去执行其它任务。主线程留着。线程不阻塞,代码逻辑上异步。非阻塞异步。虽然线程没阻塞,但任务逻辑上我们要等其它任务完成。这叫非阻塞同步。

然后我们学到了协程可以模拟线程的交替运行。单个线程也可以实现异步。

篇幅有限,协程的很多东西都没讲,比如为什么叫协程这个名字。具体如何并发编程,他和线程比有什么优势。还有个yield的升级 yield from 也没讲。python为了让协程更好的并发有哪些改进,比如生成器只能产出值,加了send()使我们可以给协程传值。
但是不用怕,可能会迟到,但不会缺席。

  • 22
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

又跑神了吗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值