来自gevent文档:
greenlet都在相同的OS线程中运行并且是协同安排的。
那么是否仍然需要使用gevent lock原语或gevent.Queue来避免单个线程中多个greenlet之间的竞争条件?一个证明这种竞争条件的例子将非常受欢迎。根据我自己的理解,这些同步原语似乎只是一种在greentlet之间切换执行流的方法。
是的,通常,仍然需要在gevent中使用锁定和同步构造。
在线程和gevent之间的锁定和同步构造,例如RLock,Semaphore和Queue,通过保护对关键数据或关键代码段的访问来确保程序状态在内部是一致的(实际上,假装这样的部分或部分数据自行运行)。
greenlet和线程之间的区别在于,虽然线程上下文更改可能在理论上完全在您的控件之外发生,但greenlet上下文更改只能在特定的定义时刻发生,因此从理论上讲,如果您在编程时非常小心,完全控制关键部分或数据的使用方式,可以完全避免切换,无需锁定。有时这很容易做,有时则不然,具体取决于程序。在gevent中,当IO,time.sleep()
等都可以导致切换时,如果有很多代码复杂性,则很难完全确定没有切换,因此关于同步和锁定的标准规则是最好的。
这是一个例子。假设我们想要将一些消息(结构化数据)写入文件或类文件对象。让我们想象一下,消息以流式方式组合在一起,一次一个块,但是接收者需要能够一起读取消息 - 两个不同消息的散布块会导致乱码。
def generate_data(chunks):
# This task generates the data that makes up a message
# in chunks.
# Imagine that each chunk takes some time to generate.
# Maybe we're pulling data from a database.
for chunk in chunks:
yield chunk
def worker_one(file):
file.write("begin message")
for chunk in generate_data('abcde'):
file.write(chunk)
file.write("end message")
def worker_two(file):
file.write("begin message")
for chunk in generate_data('123456'):
file.write(chunk)
file.write("end message")
output_file = get_output_file()
workers = [gevent.spawn(worker_one, output_file),
gevent.spawn(worken_two, output_file)]
gevent.joinall(workers)
如果get_output_file
只返回open('/some/file')
,这将工作正常:使用常规file
对象不与gevent循环合作,因此每个工作程序将运行完成而不会屈服并且消息将完好无损。
但是,如果它返回socket.create_connection(("some.host", 80)).makefile()
,这将失败并且消息将被分段。每个从一个工作者写入套接字可以让greenlet产生,另一个greenlet运行,导致数据乱码。
如果generate_data
更复杂,可能通过gevent套接字与服务器或数据库通信,那么即使我们写入文件,消息也可能因为greenlet在生成数据的过程中切换而出现乱码。
这是为什么共享状态(在这种情况下是套接字)可能需要使用同步构造进行保护的示例。