0

Firstly the following code ideally should run the 3 greenlets synchronously, but instead it runs all 3 greenlets asynchronously. However the strange thing happening is it starts an additional synchronous process for the second greenlet no matter how many greenlets you have. I am not asking this question for an workaround, its just that I want to understand the reason behind this.

import gevent
import time

def func(i):
    t = time.time()
    print('func %s started at %s' % (i, t))
    secs = 5
    statement = "after %s secs" % secs
    gevent.sleep(secs)
    print('func %s stopped in %s secs' % (i, (time.time() - t)))

apps = [gevent.Greenlet.spawn(func, i) for i in range(3)]

[app.run() for app in apps]

Here's the sample stdout:

func 0 started at 1491859273.2895772
func 1 started at 1491859273.2898045
func 2 started at 1491859273.2899446
func 0 stopped in 5.0095603466033936 secs
func 1 started at 1491859278.2993205
func 1 stopped in 5.0163233280181885 secs
func 2 stopped in 5.019707918167114 secs
func 1 stopped in 5.009198188781738 secs

How is func 1 started happening twice?

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
Avik Samaddar
  • 411
  • 4
  • 7
  • Could you be more explicit about the concrete observations you're actually seeing, and how they differ from what you expect? (Some explanation of *why* you expect the behaviors you do wouldn't hurt either). – Charles Duffy Apr 10 '17 at 20:26
  • Is my question more clear from the edits above? – Avik Samaddar Apr 10 '17 at 21:27
  • This helps inasmuch as it provides a clear description of what your current behavior is -- but what's still missing is an explanation of why you expect contrary/different behavior. – Charles Duffy Apr 10 '17 at 23:41
  • `apps` in the module is creating 3 greenlet objects(tasks), but when They are running(`run()`) later, it first make the three tasks run simultaneously, but after the first task is finished it runs the second task again. It seems more logical if 3 tasks have been carried out not 4. – Avik Samaddar Apr 11 '17 at 10:05

1 Answers1

0

Greenlets are already scheduled for invocation when you invoke spawn(). Calling run() directly is not part of the publicly documented API, and its effects are undefined. Any description of why present behavior is observed, then, would need to go beyond the documented API into implementation details.

Use gevent.joinall() to wait for your already-scheduled jobs to complete.

apps = [gevent.Greenlet.spawn(func, i) for i in range(3)]
gevent.joinall(apps)

correctly yields:

func 0 started at 1491921603.6
func 1 started at 1491921603.6
func 2 started at 1491921603.6
func 0 stopped in 5.00121307373 secs
func 1 stopped in 5.00118613243 secs
func 2 stopped in 5.0011780262 secs

Now, let's dig into that undefined behavior a bit, with the understanding that it's prone to vary release-to-release or system-to-system.

The reason it's specifically Greenlet 1 that's run twice is an accident of when the primary thread of control first cooperatively yields. You can change this behavior by yielding explicitly before calling run() the first time:

print "=== About to spawn greenlets..."
apps = [gevent.Greenlet.spawn(func, i) for i in range(3)]
print "=== All greenlets spawned; yielding..."
gevent.sleep(0)
print "=== Calling a duplicate run() invocation on each"
result = [app.run() for app in apps]

With that change, it's greenlet 0 that starts first -- and you'll note that it starts before run() is ever called at all:

=== About to spawn greenlets...
=== All greenlets spawned; yielding...
func 0 started at 1491921486.57
func 1 started at 1491921486.57
func 2 started at 1491921486.57
=== Calling a duplicate run() invocation on each
func 0 started at 1491921486.57
func 0 stopped in 5.00335502625 secs
func 1 stopped in 5.00336790085 secs
func 2 stopped in 5.0033519268 secs
func 0 stopped in 5.0033428669 secs
Charles Duffy
  • 280,126
  • 43
  • 390
  • 441