We're sorry but this page doesn't work properly without JavaScript enabled. Please enable it to continue.
Feedback

Gevent: asynchronous I/O made easy

00:00

Formal Metadata

Title
Gevent: asynchronous I/O made easy
Title of Series
Part Number
25
Number of Parts
119
Author
License
CC Attribution 3.0 Unported:
You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal purpose as long as the work is attributed to the author in the manner specified by the author or licensor.
Identifiers
Publisher
Release Date
Language
Production PlaceBerlin

Content Metadata

Subject Area
Genre
Abstract
Daniel Pope - gevent: asynchronous I/O made easy gevent provides highly scalable asynchronous I/O without becoming a nest of callbacks, or even needing code changes. Daniel will explain how to get started with gevent, discuss patterns for its use and describe the differences with Twisted, Tornado and Tulip/asyncio. ----- It has been claimed "Callbacks are the new GOTO". Most asynchronous IO libraries use callbacks extensively. gevent uses coroutines to provide highly scalable asynchronous I/O with a synchronous programming model that doesn't need code changes and callbacks. By elegantly monkey patching the Python standard library, both your code and all pure Python libraries become asynchronous too, making a separate collection of protocol implementations (in the style of Twisted) unnecessary. Code written like this is easier to understand, particularly for more junior developers. Crucially, IO errors can be raised at the right places. I will be introducing gevent's programming model, why it's easier, walk through simple code samples, and discuss experiences and metaphors for programming with it.
Keywords
Square numberStudent's t-testEvent horizonPattern languageLattice (order)Machine codeProgramming paradigmSoftware developerSoftware frameworkOperator (mathematics)Computer programmingSynchronizationDirection (geometry)Physical systemSoftwareDependent and independent variablesTheoryBroadcast programmingConsistencyForm (programming)Point (geometry)Web-DesignerPlanningMachine codeStrategy gameComputer animationLecture/Conference
Server (computing)Inversion (music)Computer fileBlock (periodic table)Loop (music)Event horizonMachine codeTorusEndliche ModelltheorieVideo gameElectronic mailing listOperator (mathematics)Reading (process)Mathematical analysisProcess (computing)System callMoment (mathematics)PlanningRight angleFunctional (mathematics)Loop (music)Connected spaceNetwork socketException handlingConnectivity (graph theory)Line (geometry)Physical systemWeightStudent's t-testComputer programmingDiagramObject (grammar)Level (video gaming)Kernel (computing)Point (geometry)Green's functionBitError messageShared memoryNumberComputer fileScaling (geometry)Semiconductor memoryEvent horizonSource codeAreaExterior algebraStreaming mediaQuicksortMachine codeSoftwareOrder (biology)Thread (computing)Game controllerSound effectMechanism designBefehlsprozessorBroadcasting (networking)MereologyProgramming languageMultiplication signFamilyClient (computing)SpacetimeTelecommunicationDirection (geometry)Function (mathematics)Similarity (geometry)Limit (category theory)ScalabilityFood energyVirtual machineSelectivity (electronic)GoogolPatch (Unix)Uniform resource locatorLibrary (computing)Stack (abstract data type)MultilaterationServer (computing)Buffer solutionHeegaard splittingQueue (abstract data type)TheoryComputer animation
Loop (music)Event horizonMeta elementDependent and independent variablesConnected spaceClient (computing)Electronic data interchangeExecution unitCoroutineElectric generatorLoop (music)QuicksortException handlingMereologyError messageElectric generatorData structureProcess (computing)Library (computing)Cartesian coordinate systemMultiplication signDependent and independent variablesConnectivity (graph theory)Line (geometry)Similarity (geometry)Attribute grammarOperator (mathematics)Communications protocolPhysical systemBitType theoryCoroutineTransportation theory (mathematics)Message passingState of matterAlgebraic closureFunctional (mathematics)Social classParameter (computer programming)SoftwareQueue (abstract data type)Keyboard shortcutComputer programmingMachine codeCycle (graph theory)Object (grammar)Level (video gaming)Connected spaceImaginary numberReading (process)Complex (psychology)Endliche ModelltheorieControl flowCellular automatonSatelliteVarianceSpacetimeInternetworkingEvent horizonStreaming mediaAreaNetwork topologyReal numberWeightSystem callGrand Unified TheoryAxiom of choiceMultiplicationDescriptive statisticsModule (mathematics)Set (mathematics)NumberSemantics (computer science)SummierbarkeitComputer animation
Gamma functionElectric generatorCoroutineEvent horizonLoop (music)Green's functionThread (computing)Event horizonFrame problemOperating systemLibrary (computing)Object (grammar)Type theoryComputer programmingStandard deviationLevel (video gaming)Food energyCollaborationismIdentity managementField (computer science)Semantics (computer science)Point (geometry)WebsiteReading (process)outputMultiplication signChainRevision controlPower (physics)Machine codeSoftware testingCross-correlationFinite-state machineRandomizationStapeldateiBitMereologyNetwork topologyElectric generatorUtility softwareLogicMachine codeKernel (computing)Control flowPopulation densityOperator (mathematics)Normal (geometry)Solid geometrySoftwareDifferent (Kate Ryan album)Bit rateCASE <Informatik>Term (mathematics)Distribution (mathematics)Line (geometry)Loop (music)Letterpress printingDivision (mathematics)Functional (mathematics)Single-precision floating-point formatGreen's functionProduct (business)Inheritance (object-oriented programming)PRINCE2INTEGRALPatch (Unix)BefehlsprozessorSound effectSummierbarkeitQuicksortUsabilityComputer animationDiagram
SynchronizationMessage passingServer (computing)Process (computing)Order (biology)Data modelComputer programmingConcurrency (computer science)Process (computing)Operator (mathematics)Exception handlingStack (abstract data type)Frame problemSpring (hydrology)Core dumpMessage passingMultiplication signResultantMereologyDirection (geometry)Exterior algebraLibrary (computing)Endliche ModelltheorieBlock (periodic table)Sheaf (mathematics)Context awarenessScalabilityNetwork topologyPhase transitionFunctional (mathematics)Physical systemProgramming paradigmCASE <Informatik>Data managementBranch (computer science)AreaGenetic programmingReading (process)SummierbarkeitFundamental unitSoftware frameworkMechanism designMatching (graph theory)Concurrency (computer science)Green's functionEvent horizonNeuroinformatikSoftwareSynchronizationLoop (music)Device driverDeadlockConnectivity (graph theory)DatabaseEquivalence relationComputer programmingMultiplicationThread (computing)BefehlsprozessorPoint (geometry)Type theoryLogicSingle-precision floating-point formatCartesian coordinate systemWage labourFront and back endsGraph coloringGame controllerComplete metric spaceQuicksortPrimitive (album)Streaming mediaObject (grammar)Server (computing)Machine codeQueue (abstract data type)Error messageSystem callMathematicsUsabilityPatch (Unix)Memory managementMehrprozessorsystemComputer fileComputer animation
System callState of matterTask (computing)Software frameworkRevision controlEvent horizonThread (computing)NumberMultiplication signLocal ringMathematical optimizationWeightObject (grammar)Semiconductor memoryOperating systemLoop (music)WindowComputing platformDatabaseDevice driverCoroutineLibrary (computing)Standard deviationPatch (Unix)Cross-platformPhysical systemBlock (periodic table)Axiom of choiceSimilarity (geometry)Machine codeProduct (business)Object-relational mappingRight angleSelectivity (electronic)Concurrency (computer science)Dependent and independent variablesGamma functionSet (mathematics)Presentation of a groupMoment (mathematics)Category of beingOntologyElectric generatorScalabilityProjective planeProteinLogicFitness functionEstimatorGreen computingProcess (computing)MereologyOperator (mathematics)Food energyReading (process)Degree (graph theory)MassSoftware developerSource codeLecture/Conference
Transcript: English(auto-generated)
OK, I guess we'll get started. Thanks for coming, everybody. My name is Daniel Pope.
I'm a web developer and DevOps enthusiast. And for the past few years, I've been doing contracts, doing development and network systems, back-end systems, that kind of thing. So I'm here to talk about G-Event. G-Event is a framework for asynchronous IO
with a fully synchronous programming model. So you may be familiar with things like Tornado and Twisted and the new async IO in Python 3.4. And G-Event is a direct competitor to that. And I hope to demonstrate that it's
easier to use and more flexible than any of those things. So where this talk is going, so first of all, we'll meet G-Event and see some examples. And then I'm going to discuss the theory behind G-Event and the other, so Tornado and Twisted and async IO
and how they compare and the programming models involved. And then I might talk briefly about my experiences with G-Event. So asynchronous or evented or non-blocking IO is any form of strategy for writing network programs
where, instead of blocking and your program suspending and waiting for a response for the IO operation that you've requested, the program goes away and does something else and resumes to the point that it resumes executing your code after the IO operation is
completed. Cheers. So diving into G-Event, this is a very simple G-Event program. So to talk through the code, the only G-Event component
we're using here is the stream server. We pass in a connection handler. And then for any connection that is received on that port, the connection handler is called. Make file is a feature of socket.
And obviously, so we're just echoing back lines. Make file turns it into a file-like object, can iterate that for lines, and send them back over the same socket. So that's very similar to the code that you might write with plain Python before 3.3, if you were picking up async IO,
except that some magic happens so that that is highly scalable. So we'll talk about the magic later. So this is a client that uses G-Event. And what we're using here is URL lib to read 100 URLs on a pool of 20 thread-like objects,
greenlets. So those things are happening more or less in parallel, so that somewhere in the middle of URL lib 2, we access a URL. It does some blocking to read data from that URL.
But while that's happening, and while data is being received, other things are scheduled and run. So the clever thing here, apart from this pool object, is that we are able to use URL lib 2 unmodified because of the first two lines, where we do some clever monkey
patching. And people keep saying, oh, monkey patching, that's a bit nasty. And I'll explain a bit later why I think it's actually rather elegant rather than rather ugly. So another example. I actually had, I could execute these examples,
but we probably don't have time. And the network might not be working. So an echo server is not particularly fascinating to look at. A chat server more so, but yeah, you probably wouldn't be able to connect to my machine. So what's happening here, we've
got reader and writer, well, greenlets, effectively. So what we're going to do is run each of those reader and writer objects in a single greenlet. So they will be happening effectively concurrently. And the reader is just reading lines from a file
and rebroadcasting them. We've got a system of queues so that a broadcast can go to all of the subscribed users. And the second part of this is the code that hooks it together. So after receiving a connection, again, this is hosted with the stream server. After receiving a connection, we turn it into a file,
read name as a function that I've not included in this, where we just read one line that is your username and loop until it's a valid username. And then create a queue for the user and split out onto two greenlets to actually just do one direction of the communication at a time.
And the join all and the try finally ensure that those greenlets, when they, because greenlets raise an exception if the connection's lost, it will just remove the user when the connection's lost.
So moving on to some theory. So talking about async and Python, the first thing I need to talk about is synchronous IO. So this is a diagram of call stack. And obviously, call stacks can be arbitrarily deep. So I've got an example of a simple call stack where we want to get some data
and that data is, so I don't know, we're going to process that data in the green function. And we want to do one IO operation, which is read one line from something, a socket probably, maybe a pipe. So in synchronous IO, we actually,
the code executes following the arrows, get to the point where we block on IO and nothing happens, that process, from our point of view, the process, it just stops dead. And the kernel then waits for IO and resumes us when that IO is ready.
So in fact, it may block more than once, but everything stays completely intact and the execution continues when the IO is complete. And obviously, then the line can be returned to the caller, which will do some processing and the data is got.
So problems with this kind of model in Python, the performance is not good and the memory usage is not good. So there is an excellent article, which I actually failed to include a URL for. So click that hyperlink.
So we'll Google that. But it turns out that threads in Python, the GIL is not a kernel level object exactly. So threads fight for the CPU attention. So it's actually much, much worse than threads in other languages.
So there's also a stack memory, so a thread. The kernel knows about threads and it prepares a bunch of stack that, where you can control it with U limit minus S, but it prepares the stack ready to do stuff in C, basically, so not particularly useful for Python.
You can turn that limit down quite low, but you won't be able to get high scalability. And similarly with processes, you've immediately lost the shared memory space, which is useful about threads, and a thread is basically a lightweight process. They're very similar kernel level objects.
Yeah, so I already mentioned this kind of model, but what we're trying to do in all of these I O systems, asynchronous I O systems is when we are doing some I O, we want to jump out of what we're doing to let other things run.
And so that usually means there is a central place, where it always means there is a central place that is waiting on I O, doing all of the waiting for all of the things that are processing I O at that moment and resuming the right one when something happens.
So this is what it might look like. So people were saying last night, don't use select, it doesn't scale, but effectively all of the alternatives to select are just sort of API improvements. But the fundamental code will look a bit like this. So something registers to want to wait
on a file descriptor for read or wait for write, and then an event loop will be started, which resumes for some definition of the function resume, when that file descriptor has the capability to do that operation. So if we register a waiter to wait for data being able to read, select will return the list
of file descriptors that on which you would then be able to, so select blocks, sorry. Select blocks, when it returns, it returns those lists,
read, write, error, and each of the items in that list is a file descriptor, and so for every file descriptor in the list read, it's guaranteed that you'll be able to read some number of bytes, which I think is like 512 or something. So there is at least 512 bytes in the buffer to be read at that point, and error handling,
obviously error handling is actually very important in network operations, so I've omitted it for brevity, but similar thing applies. And then this is the same thing, but sort of slightly modified to show the timeout, how the timeout part loops, interacts with that.
So you may be wanting to do IO and blocking on IO, but you also might want to be just blocking for an arbitrary amount of time, so you could say wait for timeout, and then the last document to select is a time that we want to suspend, and if nothing is returned on read, write, and error for the duration of that timeout,
then select will return anyway, and we could do some, process all of the things that were waiting for timeout. Right, so get into different models of what resume might mean. And so callbacks are the simplest approach,
they're used in JavaScript, Tornado IO streams, Twisted Deferred, and Async IO all have this idea of callbacks from the event loop. So this is the kind of, what this does to the stack, so whereas before we had a nice simple definition
of what get data, like get data was one thing, now I had to break it in two, we've had to break the read line function into to do the bit that sets up the getting the line, and the bit that receives the line, and or waits for a whole line perhaps.
So this is a lot more messy, and you notice that I've drawn the return values as sort of, or the return paths as light arrows, because you can't do anything useful with the return values of callbacks. They're not going to a useful place, they're not gonna contain any useful data. So there is actually no way
to ever break out of the cycle of callbacks and just turn into nice code where we can return values, which is a really convenient way of programming. So I've included some examples of callbacks, this is an imaginary HTTP library, where I'm making a request to an endpoint and passing in the function that I want to be called,
and it's got the ability to load JSON. So because I can't return it, I've got to do something, I've got to call back another callback with the response. And then if I wanted to link the request that I'm doing
to a particular state of, well, pass arguments effectively, then there's more than one way of doing it, but one way is to use closures. So the handle response is bound to the, where it's closed over beer ID,
so it knows what beer ID is, which I've not used. Oh, I've used callback instead, sorry. But sort of more practical things, this is something that I encountered a couple of years ago. This is Pico, which is an AMQP library. And here we're sort of four levels of callbacks deep,
and this is a simple example. So in fact, this just declares a queue, but you might want to declare an exchange and bind a queue to an exchange, so you're sort of six layers of callbacks deep before you've actually received a message. So I don't like that at all. That's really ugly to me.
So somebody once said, and I don't know, I don't have an attribution for this quote, but callbacks are the new go-to. And we discussed how it's an untidy code structure where you split everything into tiny little component parts and you're not able to return values. And also error handling,
you notice that in the previous example, there's absolutely no error handling in there. And if I wanted to do error handling for all of those operations, I might want to register an error handler. In fact, Pico, you register the error handler once and then it just arbitrary, some error happens somewhere in the program. But error handling with callbacks
doubles the amount of work that you're doing. So people don't and the examples don't and then people copy the examples and error handling is just left on the floor. So a simple approach to dealing with the complexity of callbacks is binding them into a class
so that you have, so rather than having the closure as I demonstrated earlier, you have a class that has method, sorry, members and methods and things and the methods, certain methods, are pre-registered as callbacks for certain operations.
So this is something that I wrote once. It's truncated perhaps, but this is a twisted application wrapping a sub-process and out received and a received get called whenever there is a chunk of data. For some reason, the process protocol
doesn't let you turn it easily into lines so I had to write that. And then how do we break out of the, this handler that's getting events, sorry, methods are being called on the object. How do we turn that into useful things in another part of the program? Had to use something called a deferred queue.
I don't really remember the semantics of it, but again, you register callbacks into it, I think. So this is just a simplification of some of the difficulties of callbacks, but it doesn't really deal with the problem and you end up using callbacks anyway
and you still have to split your processing into multiple chunks. So if I wanted to, so say I'm doing self.q.put here, but if I was to decode the lines at that point and try and do another asynchronous operation, how would I link that asynchronous operation, which is another class, to this class?
I've got to do it using callbacks, basically. And here's an example from AsyncIO. So it's exactly the same kind of thing. There's underlying a system of callbacks, but in AsyncIO, you have protocols and transports
that are paired together, so your transport is wrapping a type of thing like a, well, a subprocess or a network connection or something like that, an SSL connection, and the protocol is the processing for that, but so it's still callback-based, but you can, you're wrapping it in a class
and there's a slightly, I suppose, I think it's a slightly nicer API where, where Twisted, I had to use a process protocol and that would be different to a protocol that goes over the wire.
Sorry. Okay, okay, right, I'll try, I'll try to do that. Okay, sorry, where was I? Yeah, so I think the AsyncIO is slightly nicer
than Twisted because the protocols and the transports are kept separate, whereas in Twisted, they've been combined. But the same problems apply. So then we get into a more modern technique
of generator-based coroutines. So this is present in Tornado, there's a tornado dot gen module, and this is the, I suppose, the key feature in the new AsyncIO in Python 3.4. And so what we're doing here is we're using generators,
which, so a generator, when generators were introduced, it was noted they provide coroutine-like features. And a coroutine is really what GEvent is built in, but in this case, we are using,
so we're trying to suspend operation between the, in the place that, in the earlier example of blocking IO, we would have let the operating system call us back. We are using yield and yield from to break out of the stack
while preserving those stack frames. And the event loop will, because the event loop is the parent, the event loop knows that we're waiting for something to happen, and it will return us back to the point, it will reassemble the stack when that operation is complete.
So there's literally a division in the middle where the stack is torn down and preserved as generator objects and resumed using send. And there is, one of the advantages of this method is that you can actually return data. So in Python 3.4, where does it yield from?
3.3. So before Python 3.3, you couldn't actually return a value. You've used generators, generators in earlier Python, you could not have a yield and a return of a value. So that was a feature that had to be added.
To make this work. And also, you've used the yield. So the semantics of yield are now coupled to breaking out of the stack rather than being able to actually use generators as a useful sort of looping tool.
So this is an example of asyncio and using those coroutines. So this is a very similar example to the stack that I showed there. For some reason Sphinx wasn't able to highlight something with yield from in it. But so the asyncio.sleep is a special type of generator
that returns an object that when it's, sorry, yields an object, that when it's yielded all the way through print sum back to the event loop,
the event loop will resume that generator by going back through the print sum, sending to print sum, sending from the yield from line into the yield from asyncio.sleep line and resuming the, letting asyncio.sleep return effectively.
And this is what it looks like in Tornado. So Tornado has, Tornado works on Python two, so there is no yield from, there is no return value, but it's usable in Python two as well.
But you notice that we've had to put yields into the code where really it doesn't really make sense that we're yielding. So what is an actual coroutine? So generators have been described as,
or the approach of generators has been described as semi-coroutines, a full coroutine, can yield not just to the calling frame, but to anywhere. So any other coroutine. And it doesn't require any collaboration
from the other stack frames to do that. So the top level of the stack frame can just say, hold on, park me, I'm going to call back to the event loop. So this is a bit what it looks like. So without having to suspend the stack frame
or modify the calling conventions to use yield from, we get to the point at which we block and we just say, yield to this event loop and the event loop, when it's ready to resume, yields back to the point at which this greenlet was suspended.
So it's like blocking in the blocking IO example, except instead of blocking at the kernel level, we just suspend this greenlet, yield to the event loop, and the event loop does what the kernel would have done, which is wait for IO and resume us. So going back to the async IO example,
this is how the same piece of code would be written with gevent, rather than having to use yield from. So the only difference in terms of what you're calling is you're calling gevent.sleep. You don't need to use yield from, you write the code exactly as you would normally write it.
And somewhere in gevent.sleep, the magic happens where it yields to the event loop. And here, I'm not even kicking it off from an event loop. So the fact that I'm using gevent.sleep, gevent.sleep will create the event loop if it doesn't exist. So there's no, I don't have to be spawned
by an event loop. So much simpler. Yeah, so I think I've said most of those things. Yeah, so in gevent, the event loop is called a hub.
So let me get back to the monkey patching. So it's possible to just use gevent.monkey to modify the existing sleep function, the time.sleep function, which means, so you have to do that before you import anything,
but in case you keep references to the old versions, but it means that any existing code can run without modification. So you probably have code somewhere that uses time.sleep. You can make it run using gevent just by starting your program with gevent.monkey, import patch all, patch all,
or however you want to express that. Or maybe have a launcher that launches your program with gevent, which is an approach that's often used for something like, so it's available in GeUnicorn. You could just say, use a gevent worker, and it will do all of that stuff before your program starts. So to tackle the nastiness of monkey patching,
I don't think it's that ugly in this case, because we're not arbitrarily monkey patching bits of the standard library at random times. We're just starting, we're starting Python with a completely different distribution of the standard library that happens to be cooperative multitasking with gevent.
It's bundled as a library, rather than having like a Python-gevent program, it's bundled as a library, so you can choose to use it or not in different ways of calling your code. So for example, you might have a batch job that runs,
a batch job that runs without asynchronous code because it doesn't need it, and you want to do some CPU stuff, or simplify your code by not doing, or tests are actually better. You don't wanna do asynchronous networking stuff in tests,
so you might call your business logic as normal code and have it run synchronously, and then just when you switch into production, you're using gevent to do the asynchronous networking, and obviously you do integration tests
with actually running through gevent and that kind of thing, but it's optional to use this. You can use the full power of gevent without doing it. It just means that you can't use existing pure Python libraries, so I think that's a massive advantage. As I say here, you can't use it. If you're writing a gevent library,
you should not rely on monkey patching being present because you don't know if the caller of your library is going to want to do the monkey patching. So the monkey patching also works with async code using select, so that immediately means that you can use
existing libraries that are doing their own kind of networking, so that will have their own event loop, like Pika there. You could use Pika if you really wanted to deal with all of the callbacks, but you need to ensure that it's using the select function rather than epoll or kq or any of the other more platform-specific alternatives
that are usually better. So just to quickly run through the kind of features in gevent, you obviously need to be able to spawn a greenlet to allow concurrent operations that don't block each other, so the fundamental unit of processing IO with gevent
is to have these greenlets. You spawn greenlets to do each side of like a reading and writing side. You can kill greenlets by passing an exception so that when the greenlet is resumed, or sorry, signal that greenlet to be immediately resumable,
but when it resumes, that exception will be raised, so that's actually an advantage over threads because you can't easily do that with threads in Python. And then there's a greenlet pool equivalent to multi-processing of a pool or other types of pools, so if you wanted to do paralyzed network operations, that's an easy way to do it.
And then there's synchronization primitives to ensure concurrency between, to ensure synchronization between your greenlets. It's worth noting that greenlets never run at the same, actually at the same times, unlike threads, so these things are slightly less important, but you're, because you know that you're never going
to give up control of the CPU until you hit a blocking operation. And message passing, async result is pretty neat. So you block on a single operation, that's a useful way of turning callbacks back into synchronous programming. So you want to have a synchronous programming model
because it makes it easier. So async result, you could just say, when this callback's raised, set a value into this async result, and that gives you something that you could block on as a sort of just get, you do async result dot get, and it will return you the result. Or if there's an exception, you set the exception, and the thing that's waiting on it will actually receive the exception.
So synchronous error handling as well. And this is an example of using the thread killing, the greenlet killing mechanism. So you could just use a context manager like that, and you've automatically got a timeout on the contained section. So any timers, like time dot sleep, or all blocking operations could be limited
by the same timeout. And we've already met things like the stream server, but there are whiskey servers and that kind of thing. So I think I've covered this in a way, but you can have business logic that's completely unaware of g event
and unaware of the, even without the monkey patching, you can have business logic that's unaware of asynchronousness, and you pass in, say, a file like object which has been made green, and the business logic will hit that and stop without having to change all of your call stack to collaborate in doing this yield from shenanigans
to get back to the event loop to be resumed eventually. So I think that's a huge advantage, and I don't really want my business logic to have the idea of asynchronous backends potentially being part of it, and also have to deal with the occasional synchronous backend.
Yeah, so greenlets have the advantage, have all of the advantages of the kind of generator approach, but yeah, to sum it up, these things are very light.
The stack frames in Python are on the heap. So yeah, need to hurry through some stuff. Yeah, no code changes.
So it works on Windows, so I didn't mention that. A disadvantage is that it doesn't work on Python 3 at this time. So I mean, that's a big downer for some people. There is a Python 3 branch. There's a Python 3 fork. I don't, I've not tried it.
It's obviously sort of usable for some things, but that's not finished. But then we're talking about networking operations. So if you want to write a network server in Python 2 that chunks bytes around and use Python 3 for your user-facing stuff, and use asyncio or use synchronous IO just for that component,
that's something that might work for you. And wherever we have locks, we have the possibility of deadlocks. But that may exist in other async frameworks as well. So one pitfall, the biggest pitfall, is doing something that actually actually blocks
instead of doing this fake blocking where we yield to the event loop. So if you're using any C libraries, they probably do this, and you have to modify the library or use any async support in that library and wrap it up into the g-event programming model to avoid blocking.
And likewise, if you keep the CPU busy, you'll never yield to any other greenlets. So this isn't a tool for CPU-bound activities. But you could obviously use the ability of the networking features of g-event to delegate to synchronous backends that are doing heavy lifting and return it through your sort of
network plumbing applications written in g-event. So I mentioned using one greenlet per direction. You don't want to try and merge these two into one greenlet. You want to do writing in one greenlet and reading in one greenlet, because you only want to block in one place at a time. So in the writing, you're actually blocking, waiting for a message,
and blocking, sending that message. But you're not blocked on anything important at any particular time. You can obviously use this with multiprocessing, and that's a kind of approach that's used in Java and Go and Rust. So these systems do have green threads and greenlets,
but they use multiple threads underneath them. In Python, we'd have to use processes underneath them. But you can still get more scalability out of multiprocessor systems by using this approach on multiple processes. And then a couple of years ago,
when I was doing this really heavily, I wrote a micro-framework, which I think I gave a lightning talk a couple of years ago saying, never write a micro-framework. It was a really stupid idea. But there is one. So if you want to do g-event with a restful g-event and a green postgres driver that can do,
so your database operations also do this fake blocking kind of thing, then that's built into Nucleon. And in sort of revulsion at Pica and all of that callback stuff, I wrote an AMQP library that was actually forked
from another one called Pica. But so there is a, whereas most of the AMQP drivers try to be asynchronous through callbacks, this gives you a completely synchronous programming model. So remote queues can be exposed as local queues and you just iterate through the messages of a queue
or loop over getting messages from a queue rather than having a callback called every time a message is available. And all of the other AMQP operations as well. So that's that.
We have time for questions? We have five minutes for questions roughly. If anybody wants to leave, you can go to another room to end the session. If doesn't have a question, we would appreciate it if you could be before it.
Do you have some piece for the question? Otherwise, if you have a question, there's a mic. Just slide down.
Well, this is more a comment than a question, but we're skipping over that part real fast. But g-event only uses the select system call on all operating systems which gives suboptimal performance
and other platforms like Windows, for example. I don't actually, so somewhere in the heart of g-event, it will use the most appropriate thing for any particular operating system. But no, not for Windows, for example. Probably not for Windows, yeah. It's a horrible choice for cross-platform scalability,
but might be good if you know your target. I think it probably is possible to adapt g-event to be more Windows compatible because the coroutines approach is completely abstract. You could use it. Yeah, absolutely, but us now, it uses suboptimal system call on some platforms.
So it might be worth knowing if you're gonna dabble in g-event. In fairness, I've never used it on Windows, but I hear it works on Windows. No, me neither, but just worth knowing. Is there any optimal number of grill nets you can use in our process? Sorry, I can't hear you.
Is there any optimal number of grill nets you should use per one process, for example, or range, or how many grill nets should you start? How many workers? Oh, grill nets. Oh, start as many grill nets as you want. It's like just, grill nets are very, very cheap, so create as many as you need. You can start at like 1,000? Yeah, 10,000 grill nets.
Yeah, 100,000, who cares? As long as you've got memory. I have a question regarding generators and coroutines. If you use a generator, you yield, if you have an asynchronous task to do, and you exactly know that between the yield and the resume, the global state or the shared state may change,
and in a g-event, you don't see, obviously, that, for example, time sleep may suspend you, or your green light and the state may be differently when you wake up again, so. Yeah, well, so that's something to be careful of in all of the async frameworks,
that you can't really rely on global state. Something that is useful about g-event is that it has its own thread local object, so, and the monkey patch approach will apply that thread local object as the threading dot local, so that means that you can have your local state in a thread local object, and I think Flask uses thread locals
or something like that, so with monkey patching, the Flask global stuff should work, although I don't think I've ever tried that, so, yeah, but obviously, it's something to be careful about using global state when you're using anything concurrent. Thanks.
So if g-event was ported to Python 3, could it be compatible with async IO? For example, could we use some library in that scenario that's written in the async IO style? I suspect you probably can, yeah, so async IO is completely within Python,
it doesn't have any special Python tricks, whereas this one C trick in g-event and similar systems with co-routines where you could jump to a different stack, so anything completely within Python could potentially run on g-event, but I think the big hope for async IO
is that the event loop in async IO will come to be the standard event loop for everything, including something like g-event, so you could run an async IO event loop and have g-event use that as its event loop, meaning that all of the frameworks like Twisted and Tornado and g-event and async IO
could all be running on exactly the same code. As it is at the moment, you could probably use, because as I said, select is one of the things that's patched, you could probably use Tornado and Twisted, their own event loops within g-event, but actually bringing everything together is an open problem.
Okay, thanks. Hello, do you have any idea on when g-event will be ported to Python 3, because I think it's a very important dependency for a lot of projects, like hundreds of thousands of projects, and with Twisted, I think it's one of those dependencies which needs to be ported right now, you know?
So you have any estimate on when? So g-event has only just reached version one, it was in November last year that it reached version 1.0, and I don't know how much effort is going into writing a Python 3 compatible g-event, so yeah, no answer for that,
but if you're interested in it, get involved, why not? Okay, thanks. Thank you, it was a very insightful presentation. I'd like to ask you, where I work, we use Tornado for production and asynchronous Python. Sorry, I didn't hear that, it's a bit noisy, sorry. Where I work, we use Tornado as asynchronous Python,
and it works really well, just like g-event, I myself used g-event in the past. The problem is that we found a really hard bottleneck, and that is the database, because the database, we couldn't find an asynchronous ORM. Yeah, right.
So it's a huge problem for us, we don't want to use inline queries, so do you have any idea of an asynchronous ORM, or one that's being developed? So there are ways around it, if you want to do synchronous operations, you can use an actual thread pool or something like that, so wrap up the synchronous code, or a multi-processing pool,
so that you're passing the requests using g-event and receiving the responses using g-event, but you only have, say, four blocking workers that are doing database pools. That's one way around it. I think, so I've, in Nucleon, I found a way of, somebody had already written an example code for how to make the Postgres psycho PG2 driver
co-routine compatible, by having the library itself tell you when it wants to block, and then you are responsible for doing that kind of blocking. So it is possible for some drivers, but there are workarounds for others.
Okay, thank you. Well, thank you very much, everyone.