为什么gevent需要同步,因为它是单线程的

Why gevent needs synchronization since it is in a single threaded

来自 gevent 文档:

The greenlets all run in the same OS thread and are scheduled cooperatively.

那么是否仍然需要使用 gevent 锁原语或 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 只是 returns open('/some/file'),这将工作正常:使用常规 file 对象不与 gevent 循环合作,因此每个工人将运行 永不屈服地完成,消息将完好无损。

但是,如果它返回 socket.create_connection(("some.host", 80)).makefile(),这将失败并且消息将被碎片化。一个 worker 对套接字的每次写入都可能让 greenlet 屈服,而另一个 greenlet 运行,导致数据出现乱码。

如果 generate_data 更复杂,可能通过 gevent 套接字与服务器或数据库通信,那么即使我们正在写入文件,消息也可能是乱码,因为 greenlet 在处理过程中切换生成数据。

这是共享状态(在本例中为套接字)可能需要使用同步结构进行保护的示例。