来自gevent docs:
greenlet都在同一个OS线程中运行,并且是合作安排的。
那么是否仍然需要使用gevent lock原语或gevent.Queue来避免单个线程中多个greenlet之间的竞争条件?一个证明这种竞争条件的例子将非常受欢迎。根据我自己的理解,这些同步原语似乎只是一种在greentlet之间切换执行流的方法。
答案 0 :(得分:4)
是的,一般来说,仍然需要在gevent中使用锁定和同步构造。
在线程和gevent(如RLock,Semaphore和Queue)下的锁定和同步构造通过保护对关键数据或关键代码段的访问来确保程序状态在内部是一致的(实际上,假装这样一个部分或数据单独运行。
greenlet和线程之间的区别在于,虽然线程上下文更改可能在任何时候理论上完全超出您的控制范围,但greenlet上下文更改只能在特定的定义时刻发生,因此从理论上讲,如果您非常小心您的编程并完全控制关键部分或数据的使用方式,您可以完全避免使用开关并消除对锁的需求。有时这很容易做,有时则不然,具体取决于程序。在gevent中,当IO,time.sleep()
等都可以导致切换时,如果有很多代码复杂性,则可能很难完全确定没有切换,因此关于同步和锁定的标准规则是最好的。
这是一个例子。假设我们想要将一些消息(结构化数据)写入文件或类文件对象。让我们想象一下,消息以流式方式组合在一起,一次一个块,但是接收者需要能够一起读取消息 - 散布两个不同消息的块会导致乱码。< / p>
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在生成数据的过程中会切换
这是为什么可能需要使用同步结构保护共享状态(在本例中为套接字)的示例。