1.简介
为了尽可能地混淆问题,请将解决方案委托给程序员;)。但是严重的是,在我看来,协程会发生类似的情况,因为无论是否愿意,协程都会使情况变得模糊。后者的特点是,仍然存在并行编程的问题,这些问题不会随处可见,最重要的是,协程对于它们的基本解决方案没有帮助。
让我们从术语开始。 “他们告诉了世界多少次”,但是到目前为止,“世界”仍然在问异步编程和并行编程之间的区别(请参阅[1]中关于异步主题的讨论)。了解异步与并行性问题的症结始于定义并行性本身。它根本不存在。有某种直观的理解,通常以不同的方式解释,但是没有科学的定义可以像讨论“二和二”运算的结果那样建设性地消除所有问题。
而且,由于这一切都还不存在,因此,在术语和概念上感到困惑,我们仍然区分并行和并行编程,异步,响应式和其他编程等等。等等我认为,实现像Felix这样的机械计算器与软件计算器的工作方式不同不会出现问题。但是从正式的角度来看一组操作和最终结果之间没有区别。在并行编程的定义中必须考虑这一原理。
我们必须有严格的定义和描述并行性的透明方法,以产生一致的结果,例如“笨拙的” Felix和任何软件计算器。 “并行性”的概念不可能与其实现方式(具有相同核的数量)相关联。而“幕后”到底有什么呢?这仅应主要针对那些从事“机械”实施的人员,而不是那些使用这种常规“并行计算器”的人员。
但是我们拥有我们所拥有的。而且,如果不是狂热的话,我们还将积极地进行协程和异步编程的讨论。如果我们似乎已经厌倦了多线程,但又没有提供其他东西,该怎么办?他们甚至谈论某种魔术;)但是,如果您了解原因,一切都会变得显而易见。它们恰好位于此处-在并行性平面中。它的定义和实现。
但是,让我们从编程科学(从那时起的计算机)的全球性和某种程度上,到我们的“有罪的地球”下降。在这里,在不影响当前流行的Kotlin语言的优点的情况下,我想承认我对Python语言的热情。也许有一天,在其他情况下,我的喜好会改变,但实际上到目前为止,一切都如此。
有几个原因。其中包括免费使用Python。这不是最有力的论据,因为具有相同Qt的示例表示情况可以随时更改。但是,尽管与Kotlin不同,Python是免费的,至少是以JetBrains相同的PyCharm环境的形式提供(对此特别感谢),但我的同情心却站在了一边。同样引人注目的是,在互联网上有大量的俄语文学作品(例如Python),既具有教育意义,又具有真实感。在Kotlin上,他们的人数并不多,种类也不是很多。
也许在曲线的前面,我决定在定义和实现软件并行性和异步性问题的背景下介绍精通Python的结果。这是由[2]条提出的...今天,我们将讨论生成协程的主题。我对它们的兴趣是因为需要了解现代语言/编程语言的特殊,有趣,但目前还不是很熟悉的可能性。
由于我实际上是一名纯C ++程序员,因此可以解释很多问题。例如,如果在Python中协程和生成器已经存在很长时间了,那么在C ++中它们还没有赢得他们的位置。但是C ++真的需要吗?我认为,编程语言需要合理扩展。似乎C ++尽可能地提高了性能,但现在它正急速追赶。但是,可以使用比协程/协程更根本的其他概念和模型来实现类似的并发问题。而且,这一声明背后不仅是言语的事实将得到进一步证明。
如果我们要接受一切,那么我也承认我对C ++相当保守。当然,它的对象和OOP功能对我来说是“我们的一切”,但是我要说对模板至关重要。好吧,我从来没有真正看过他们独特的“鸟语”,它看起来使代码的理解和对算法的理解大大复杂化。尽管有时我什至依靠他们的帮助,但一只手的手指足以应付所有这些需要。我尊重STL库,不能没有它:)因此,即使从这个事实出发,我有时也对模板有疑问。因此,我仍然尽可能地避免使用它们。现在我正在为C ++中的“模板协程”颤抖;)
Python是另一回事。我还没有注意到其中的任何图案,它使我平静下来。但是,另一方面,这很令人震惊。但是,当我查看Kotlin代码,尤其是引擎舱代码时,焦虑很快就会消失;)但是,我认为这仍然是习惯和偏见的问题。我希望随着时间的推移,我将训练自己以充分理解它们(模板)。
但是...回到协程。事实证明,现在它们的名称为corutin。更改名称有什么新功能?是的,实际上什么都没有。和以前一样,轮流考虑执行的功能。以与以前相同的方式,在退出函数之前,但在其工作完成之前,返回点是固定的,以后可以从该点恢复工作。由于没有规定切换顺序,因此程序员自己通过创建自己的调度程序来控制此过程。通常,这只是功能的循环。例如,Oleg Molchanov [3]的视频中的Round Robin事件循环。
这就是协程协程和异步编程的现代介绍通常看起来像“触手可及”的样子。显然,随着对这一主题的沉浸,出现了新的术语和概念。发电机就是其中之一。此外,它们的示例将是展示“平行偏好”的基础,但是在我的自动解释中已经如此。
2.数据列表的生成器
所以-发电机。异步编程和协程通常与它们关联。 Oleg Molchanov的一系列视频讲述了这一切。因此,他将生成器的关键功能称为“生成器能够暂停执行某个功能,以便从上次停止执行的位置继续执行该功能”(有关更多详细信息,请参见[3])。而且,考虑到以上关于协程已经很古老的定义的说法,没有什么新鲜的。
但事实证明,生成器在创建数据列表时发现了相当特定的用途。Egorov Artem的视频中已经介绍了该主题[4]。...但是,看起来,通过这样的应用,我们在质量上混合了不同的概念-操作和流程。通过扩展语言的描述能力,我们在很大程度上掩盖了可能出现的问题。正如他们所说,在这里不要玩太多。在我看来,使用生成器协程描述数据确实有助于做到这一点。注意,Oleg Molchanov还警告不要将生成器与数据结构相关联,强调“生成器是函数” [3]。
回到使用生成器来定义数据。很难掩盖我们已经创建了一个计算列表项的过程。因此,关于这样的过程列表的问题立刻出现。例如,如果协程在定义上只能“单向”工作,该如何重用它?如果无法建立进程索引,如何计算其中的任意元素?等等。等等Artem没有给出这些问题的答案,只是警告说,他们说,不能组织对列表元素的重复访问,并且索引是不可接受的。在Internet上进行的搜索使我们相信,不仅我有类似的问题,而且提出的解决方案也不是那么简单和显而易见。
另一个问题是列表生成的速度。现在,我们在每个协程开关上形成列表的单个元素,这增加了数据生成时间。通过在“批”中生成元素,可以大大加快该过程。但是,很可能会有问题。如何停止已经运行的进程?或者是其他东西。仅使用选定的项目,列表可能会很长。在这种情况下,数据存储通常用于有效访问。顺便说一下,几乎立即我就在Python上找到了有关该主题的文章,请参见[5](有关自动机记忆的更多信息,请参见文章[6])。但是这种情况呢?
用于定义列表的这种语法的可靠性也可能令人怀疑,因为 错误地使用方括号而不是括号很容易,反之亦然。事实证明,在实践中看似美观而优雅的解决方案可能会导致某些问题。编程语言应具有技术性,灵活性,并应确保避免非自愿性错误。
顺便说一句,在有关列表和生成器的优缺点的主题上,与上述内容相交,您可以观看Oleg Molchanov [7]的另一段视频。
3.发电机协程
Oleg Molchanov [8] 的下一个视频讨论了如何使用生成器来协调协程的工作。实际上,它们是为此目的而设计的。请注意切换协程的时间选择。它们的排列遵循一个简单的规则-我们将yield语句放在阻塞函数的前面。后者被理解为函数,与其他操作相比,返回时间如此之长,以至于计算与停止它们有关。因此,它们被称为阻断剂。
当挂起的进程继续进行其工作时,恰好在阻塞调用不会等待但将快速完成其工作时,切换才有效。而且,为此,似乎所有这些“麻烦”都是围绕协程/协程模型开始的,因此,推动了异步编程的发展。尽管请注意,协程的原始想法仍然不同-创建并行计算的虚拟模型。
在所考虑的视频中,与协程的一般情况一样,协程操作的继续由事件调度程序外部环境决定。在这种情况下,它由名为event_loop的函数表示。而且,似乎所有事情都是合乎逻辑的:调度程序将在需要时通过调用next()运算符执行分析并继续协程的工作。问题出在等待之外的地方:调度程序可能非常复杂。在Molchanov的上一个视频(请参阅[3])中,一切都很简单,因为进行了简单的交替控制转移,其中没有锁,因为没有相应的电话。尽管如此,我们强调在任何情况下至少都需要一个简单的调度程序。
问题1 , next() (. event_loop). , , yield. - , , next(), .
2. , select, — . .
但是,重点甚至不是需要计划者,而是他承担着对他来说不寻常的功能这一事实。由于必须实现用于许多协程的联合操作的算法,这一事实使情况更加复杂。Oleg Molchanov在提到的两个视频中讨论的调度程序的比较非常清楚地反映了一个类似的问题:[8]中的套接字调度算法明显比[3]中的“轮播”算法复杂。
3.走向没有协程的世界
既然我们确信没有协程的世界是可能的,可以用自动机对付它们,那么有必要展示它们已经解决了类似的任务。让我们使用处理套接字的相同示例来演示这一点。请注意,它的最初实现并不是那么简单,以至于可以立即理解。视频作者本人反复强调这一点。其他人在协程中也面临类似的问题。因此,协程的缺点与它们的感知,理解,调试等复杂性有关。在视频中讨论过[10]。
首先,简要介绍一下所考虑算法的复杂性。这归因于客户服务流程的动态性和多元性。为此,需要创建一个在给定端口上侦听的服务器,并在出现请求时生成到达该端口的许多客户端服务功能。由于可以有许多客户端,因此它们的出现是无法预测的,因此会根据服务套接字和与之交换信息的过程创建一个动态列表。清单1中显示了视频[8]中讨论的Python生成器解决方案的代码。
清单1.生成器上的套接字
import socket
from select import select
tasks = []
to_read = {}
to_write = {}
def server():
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(('localhost', 5001))
server_socket.listen()
while True:
yield ('read', server_socket)
client_socket, addr = server_socket.accept()
print('Connection from', addr)
tasks.append(client(client_socket, addr))
print('exit server')
def client(client_socket, addr):
while True:
yield ('read', client_socket)
request = client_socket.recv(4096)
if not request:
break
else:
response = 'Hello World\n'.encode()
yield ('write', client_socket)
client_socket.send(response)
client_socket.close()
print('Stop client', addr)
def event_loop():
while any([tasks, to_read, to_write]):
while not tasks:
ready_to_read, ready_to_write, _ = select(to_read, to_write, [])
for sock in ready_to_read:
tasks.append(to_read.pop(sock))
for sock in ready_to_write:
tasks.append(to_write.pop(sock))
try:
task = tasks.pop(0)
reason, sock = next(task)
if reason == 'read':
to_read[sock] = task
if reason == 'write':
to_write[sock] = task
except StopIteration:
print('Done!')
tasks.append(server())
event_loop()
服务器和客户端算法是相当基本的。但是应该警告服务器将客户端功能放在任务列表中。进一步-更多:难以理解event_loop事件循环的算法。如果至少应该始终存在服务器进程,直到任务列表可以为空为止。..
接下来,引入字典to_read和to_write。字典的工作需要单独解释,因为这比处理常规列表要困难得多。因此,yield语句返回的信息是针对它们量身定制的。然后,围绕着字典开始“铃鼓跳舞”,一切都变得像一种“沸腾的东西”:字典中似乎放置了一些东西,从那里进入了任务列表,依此类推。等等您可以“断头”,理清所有这些。
而手头任务的解决方案将是什么样?对于自动机,创建与视频中已经讨论过的套接字等效的模型是合乎逻辑的。在服务器模型中,似乎不需要进行任何更改。这将是一个自动机,其功能类似于server()函数。其图如图所示。 1a。自动机动作y1()创建一个服务器套接字,并将其连接到指定的端口。谓词x1()定义了客户端连接,并且y2()操作(如果存在)创建了一个客户端套接字服务进程,将其放置在包括活动对象类的类进程列表中。
在图。图1b示出了针对单个客户的模型的图。处于状态“ 0”时,自动机确定客户端是否准备发送信息(谓词x1()-true),并在转换到状态“ 1”时在动作y1()中接收响应。此外,当客户端准备好接收信息时(已经x2()必须为true),动作y2()实现在向初始状态“ 0”过渡时向客户端发送消息的操作。如果客户端断开与服务器的连接(在这种情况下,x3()为false),则自动机将切换到状态“ 4”,并在y3()操作中关闭客户端套接字。该过程将保持状态“ 4”,直到将其从活动类列表中排除为止(有关列表的形成,请参见服务器模型的上述说明)。
在图。图1c显示了一个自动机,该自动机实现了类似于清单1中的event_loop()函数的启动过程。只有在这种情况下,其操作算法才简单得多。一切都归结为这样一个事实:机器会遍历活动类列表的元素,并为每个类调用loop()方法。此动作由y2()实现。 y4()操作从列表中排除状态为“ 4”的类。其余操作与对象列表的索引一起使用:y3()操作增加索引,y1()操作将其重置。
Python中的对象编程功能与C ++中的对象编程不同。因此,将以自动机模型的最简单实现为基础(确切地说,它是对自动机的模仿)。它基于表示过程的对象原理,其中每个过程对应一个单独的活动类(它们通常也称为代理)。该类包含必要的属性和方法(请参见有关特定自动机方法的更多详细信息[9]中的谓词和操作),并且自动机操作的逻辑(其转移和退出函数)集中在称为loop()的方法的框架内。为了实现自动机行为的逻辑,我们将使用if-elif-else构造。
使用这种方法,“事件循环”与分析套接字的可用性无关。它们由流程本身检查,这些流程在谓词中使用相同的select语句。在这种情况下,它们使用单个套接字(而不是它们的列表)进行操作,请检查该套接字是否有针对该特定套接字的预期操作,并且恰好在操作算法确定的情况下进行检查。顺便说一下,在调试这种实现的过程中,出现了select语句的意外阻塞本质。
数字:1.用于套接字的自动机过程图
清单2显示了Python中用于处理套接字的自动机对象代码,这就是我们所说的“没有协程的世界”。这是一个具有不同的软件设计原则的“世界”。它的特点是存在并行计算的算法模型(有关更多详细信息,请参见[9],这是自动机编程技术(AP)与“协程技术”之间的主要和质量上的区别。
自动机编程可轻松实现程序设计,过程并行性以及程序设计人员可以想到的所有内容的异步原理。我以前的文章从自动计算的结构模型及其正式定义到其应用示例的描述开始,对此进行了更详细的描述。上面的Python代码演示了协程的协程原理的自动实现,将它们完全重叠,并用状态机模型进行了补充和扩展。
清单2.计算机上的套接字
import socket
from select import select
timeout = 0.0; classes = []
class Server:
def __init__(self): self.nState = 0;
def x1(self):
self.ready_client, _, _ = select([self.server_socket], [self.server_socket], [], timeout)
return self.ready_client
def y1(self):
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.server_socket.bind(('localhost', 5001))
self.server_socket.listen()
def y2(self):
self.client_socket, self.addr = self.server_socket.accept()
print('Connection from', self.addr)
classes.append(Client(self.client_socket, self.addr))
def loop(self):
if (self.nState == 0): self.y1(); self.nState = 1
elif (self.nState == 1):
if (self.x1()): self.y2(); self.nState = 0
class Client:
def __init__(self, soc, adr): self.client_socket = soc; self.addr = adr; self.nState = 0
def x1(self):
self.ready_client, _, _ = select([self.client_socket], [], [], timeout)
return self.ready_client
def x2(self):
_, self.write_client, _ = select([], [self.client_socket], [], timeout)
return self.write_client
def x3(self): return self.request
def y1(self): self.request = self.client_socket.recv(4096);
def y2(self): self.response = 'Hello World\n'.encode(); self.client_socket.send(self.response)
def y3(self): self.client_socket.close(); print('close Client', self.addr)
def loop(self):
if (self.nState == 0):
if (self.x1()): self.y1(); self.nState = 1
elif (self.nState == 1):
if (not self.x3()): self.y3(); self.nState = 4
elif (self.x2() and self.x3()): self.y2(); self.nState = 0
class EventLoop:
def __init__(self): self.nState = 0; self.i = 0
def x1(self): return self.i < len(classes)
def y1(self): self.i = 0
def y2(self): classes[self.i].loop()
def y3(self): self.i += 1
def y4(self):
if (classes[self.i].nState == 4):
classes.pop(self.i)
self.i -= self.i
def loop(self):
if (self.nState == 0):
if (not self.x1()): self.y1();
if (self.x1()): self.y2(); self.y4(); self.y3();
namSrv = Server(); namEv = EventLoop()
while True:
namSrv.loop(); namEv.loop()
清单2中的代码比清单1中的代码在技术上要先进得多。这就是自动计算模型的优点。通过将自动机行为集成到编程对象模型中,可以简化此过程。结果,自动机过程的行为逻辑正好集中在生成它的位置,而不是像协程中那样委托给过程控制的事件循环。新的解决方案引发了通用“事件循环”的创建,该事件循环的原型可以视为EventLoop类的代码。
4.关于SRP和DRY原理
Oleg Molchanov [11] 在另一段视频中表达了“单一责任”的原则-SRP(“单一责任原则”)和“不要重复自己”-DRY(不要重复自己)。据他们说,该功能应只包含目标代码,以免违反SRY原则,并且不促进“额外代码”的重复,以不违反DRY原则。为此,建议使用装饰器。但是还有另一种解决方案-自动解决方案。
在上一篇文章中[2]在没有意识到这种原理存在的情况下,使用装饰器给出了一个示例。考虑了一个计数器,顺便说一句,如果需要,它可以生成列表。提及了测量计数器的运行时间的秒表对象。如果对象符合SRP和DRY原理,则其功能不如通信协议重要。在实现中,计数器代码与秒表代码无关,并且更改任何对象都不会影响其他对象。它们仅受协议约束,对象围绕该协议达成共识,然后严格遵循。
因此,并行自动机模型实质上覆盖了装饰器的功能。由于其功能更灵活,更容易实现,因为 不“包围”(不修饰)功能代码。出于客观评估和比较自动机与常规技术的目的,清单3显示了前一篇文章[2]中讨论的计数器的对象类似物,其中在评论后给出了简化版本及其执行时间和计数器的原始版本。
清单3.自动计数器实现
import time
# 1) 110.66 sec
class PCount:
def __init__(self, cnt ): self.n = cnt; self.nState = 0
def x1(self): return self.n > 0
def y1(self): self.n -=1
def loop(self):
if (self.nState == 0 and self.x1()):
self.y1();
elif (self.nState == 0 and not self.x1()): self.nState = 4;
class PTimer:
def __init__(self, p_count):
self.st_time = time.time(); self.nState = 0; self.p_count = p_count
# def x1(self): return self.p_count.nStat == 4 or self.p_count.nState == 4
def x1(self): return self.p_count.nState == 4
def y1(self):
t = time.time() - self.st_time
print ("speed CPU------%s---" % t)
def loop(self):
if (self.nState == 0 and self.x1()): self.y1(); self.nState = 1
elif (self.nState == 1): pass
cnt1 = PCount(1000000)
cnt2 = PCount(10000)
tmr1 = PTimer(cnt1)
tmr2 = PTimer(cnt2)
# event loop
while True:
cnt1.loop(); tmr1.loop()
cnt2.loop(); tmr2.loop()
# # 2) 73.38 sec
# class PCount:
# def __init__(self, cnt ): self.n = cnt; self.nState = 0
# def loop(self):
# if (self.nState == 0 and self.n > 0): self.n -= 1;
# elif (self.nState == 0 and not self.n > 0): self.nState = 4;
#
# class PTimer:
# def __init__(self): self.st_time = time.time(); self.nState = 0
# def loop(self):
# if (self.nState == 0 and cnt.nState == 4):
# t = time.time() - self.st_time
# print("speed CPU------%s---" % t)
# self.nState = 1
# elif (self.nState == 1): exit()
#
# cnt = PCount(100000000)
# tmr = PTimer()
# while True:
# cnt.loop();
# tmr.loop()
# # 3) 35.14 sec
# class PCount:
# def __init__(self, cnt ): self.n = cnt; self.nState = 0
# def loop(self):
# if (self.nState == 0 and self.n > 0):
# self.n -= 1;
# return True
# elif (self.nState == 0 and not self.n > 0): return False;
#
# cnt = PCount(100000000)
# st_time = time.time()
# while cnt.loop():
# pass
# t = time.time() - st_time
# print("speed CPU------%s---" % t)
# # 4) 30.53 sec
# class PCount:
# def __init__(self, cnt ): self.n = cnt; self.nState = 0
# def loop(self):
# while self.n > 0:
# self.n -= 1;
# return True
# return False
#
# cnt = PCount(100000000)
# st_time = time.time()
# while cnt.loop():
# pass
# t = time.time() - st_time
# print("speed CPU------%s---" % t)
# # 5) 18.27 sec
# class PCount:
# def __init__(self, cnt ): self.n = cnt; self.nState = 0
# def loop(self):
# while self.n > 0:
# self.n -= 1;
# return False
#
# cnt = PCount(100000000)
# st_time = time.time()
# while cnt.loop():
# pass
# t = time.time() - st_time
# print("speed CPU------%s---" % t)
# # 6) 6.96 sec
# def count(n):
# st_time = time.time()
# while n > 0:
# n -= 1
# t = time.time() - st_time
# print("speed CPU------%s---" % t)
# return t
#
# def TestTime(fn, n):
# def wrapper(*args):
# tsum=0
# st = time.time()
# i=1
# while (i<=n):
# t = fn(*args)
# tsum +=t
# i +=1
# return tsum
# return wrapper
#
# test1 = TestTime(count, 2)
# tt = test1(100000000)
# print("Total ---%s seconds ---" % tt)
让我们在一个表中总结各种选项的工作时间,并对工作结果进行评论。
- 经典自动机实施-110.66秒
- 没有自动机方法的自动机实现-73.38秒
- 没有自动秒表-35.14
- 表单中的计数器,每次迭代时都有输出-30.53
- 带有阻塞周期的计数器-18.27
- 带装饰器的原创柜台-6.96
第一个选项代表完整的自动计数器模型,即 计数器本身和秒表的运行时间最长。可以说,放弃自动技术的原理可以减少运行时间。因此,在选项2中,对谓词和操作的调用将替换为其代码。这样我们节省了方法调用运算符的时间,这是非常明显的,即 减少了30秒钟以上,减少了操作时间。
我们在第3个变体中节省了更多时间,创建了一个更简单的计数器实现,但是在计数器循环的每个迭代(模仿协程操作)中都退出了它。通过消除计数器的悬挂(请参阅选项5),我们最大程度地减少了计数器的工作量。但是与此同时,我们失去了协程工作的优势。选项6-这是带有装饰器的原始计数器,该计数器已经重复并且运行时间最短。但是,与选项5一样,这是一个阻塞的实现,在讨论函数的协程操作时,它不适合我们。
5。结论
使用自动机技术还是信任协程-决定权完全由程序员决定。在这里对我们很重要,他知道程序设计中存在与协程不同的方法/技术。您甚至可以想象以下奇特的选择。首先,在模型设计阶段,创建自动机解决方案模型。它是严格科学的,循证的和有据可查的。然后,例如,为了提高性能,将其“变形”为代码的“正常”版本,如清单3所示,您甚至可以想象代码的“反向重构”,即代码。从7选项1日的过渡,但这个,虽然有可能,但最有可能一些事件:)的
在图。图2显示了有关“异步” [10]主题的视频幻灯片。... 而且,“坏”似乎比“好”重。如果我认为自动机总是很好的话,那么在异步编程的情况下,请按照他们的喜好选择。但看起来“坏”选项最有可能出现。并且程序员在设计程序时应事先了解这一点。
数字:2.异步编程的特点
当然,自动机代码有些“不是没有罪”。它将具有稍微更多的代码量。但是,首先,它具有更好的结构,因此更易于理解和维护。其次,它不会总是更大,因为 随着复杂性的增加,最有可能获得回报(例如,由于重用自动机方法),调试起来更容易,更清晰。是的,归根结底是完全SRP和DRY。有时,这远远超过了。
假设,比如说,功能设计的标准是合乎需要的,甚至可能是必要的。程序员应尽可能避免设计阻塞功能。为此,它必须要么仅启动计算过程,然后检查其完整性,要么具有检查启动准备程度的方法,如示例中考虑的选择功能。如清单4所示,该代码使用的功能可以追溯到DOS时代,它表明此类问题具有悠久的“前例程”历史。
清单4.从键盘读取字符
/*
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
int C=0;
while (C != 'e')
{
C = getch();
putchar (C);
}
return a.exec();
}
*/
//*
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
int C=0;
while (C != 'e')
{
if (kbhit()) {
C = getch();
putch(C);
}
}
return a.exec();
}
//*/
这是从键盘读取字符的两个选项。第一种选择是阻止。它将阻塞计算,并且直到getch()函数从键盘接收到该字符后,该语句才会运行以输出该字符。在第二个变体中,只有在配对的函数kbhit()确认字符在输入缓冲区中时,才在正确的时间启动相同的函数。因此,将不会阻塞计算。
如果该函数本身是“繁重的”,即需要花费大量的时间来工作,并且通过协程的工作类型周期性地退出(这可以在不使用相同协程的机制的情况下完成,以便不与之绑定),这很难做到或没有多大意义,那么仍然需要将此类功能放在单独的线程中,然后控制其工作的完成(请参见[2]中QCount类的实现)。
您总能找到排除计算阻塞的出路。上面,我们展示了如何在语言的常规方法框架内创建异步代码,而无需使用协程/协程机制,甚至不使用任何专用环境,例如VKP(a)自动编程环境。以及如何使用以及如何使用由程序员决定。
文学
1. Python Junior播客。关于python中的异步。 [电子资源],访问模式:www.youtube.com/watch?v=Q2r76grtNeg,免费。语言。俄语(治疗日期07/13/2020)。
2.并发性和效率:Python与FSM。 [电子资源],访问方式:habr.com/ru/post/506604,免费。语言。俄语(治疗日期07/13/2020)。
3. Molchanov O. Python#4中的异步基础:生成器和Round Robin事件循环。 [电子资源],访问方式:www.youtube.com/watch?v=PjZUSSkGLE8 ],免费。语言。俄语(治疗日期07/13/2020)。
4. 48个生成器和迭代器。 Python中的生成器表达式。 [电子资源],访问方式:www.youtube.com/watch?v=vn6bV6BYm7w, 自由。语言。俄语(治疗日期07/13/2020)。
5.记忆和咖喱(Python)。 [电子资源],访问方式:habr.com/ru/post/335866,免费。语言。俄语(治疗日期07/13/2020)。
6. Lyubchenko V.S.关于处理递归。 “ PC World”,第11/02号。www.osp.ru/pcworld/2002/11/164417
7. Molchanov O. Python的课程#10-什么是收益。 [电子资源],访问模式:www.youtube.com/watch?v=ZjaVrzOkpZk,免费。亚兹俄语(治疗日期07/18/2020)。
8. Molchanov O. Python#5中的异步基础:生成器上的异步。 [电子资源],访问模式:www.youtube.com/watch?v=hOP9bKeDOHs,免费。亚兹俄语(治疗日期07/13/2020)。
9.并行计算模型。[电子资源],访问方式:habr.com/ru/post/486622,免费。语言。俄语 (治疗日期07/20/2020)。
10. Polishchuk A. Python中的异步性。[电子资源],访问模式:www.youtube.com/watch?v=lIkA0TDX8tE,免费。语言。俄语 (治疗日期07/13/2020)。
11. Molchanov O.经验教训Python cast#6-装饰器。[电子资源],访问模式:www.youtube.com/watch?v=Ss1M32pp5Ew,免费。语言。俄语 (治疗日期07/13/2020)。