[Translated] [Reproduced] greenlet: lightweight concurrent program, greenlet
Https://greenlet.readthedocs.io/en/latest/
Address: https://zhuanlan.zhihu.com/p/25188731
Background
The greenlet package is a derivative of Stackless. It is a CPython version that supports micro-threads (tasklets. Tasklets runs in pseudo-concurrency mode (usually at one or a few OS-level threads), and they use "channels" to interact with data.
On the other hand, a "greenlet" is a primitive concept about micro-threads without internal scheduling. In other words, "greenlet" is a useful method when you want to implement accurate control during code execution. Based on greenlet, you can define your own micro-thread scheduling policy. In any case, greenlets can also be used by themselves in an advanced control flow structure. For example, we can regenerate the iterator. The difference between the built-in python generator and the greenlet generator is that the greenlet generator can call functions nested, And the nested function also has the yield value (note that you do not need to use the yield keyword, see example: test_generator.py ).
Example
Let's consider a terminal console system where users enter commands. Assume that the input is character-by-character input. In such a system, there is a typical cycle as follows:
def process_commands(*args): while True: line = '' while not line.endswith('\n'): line += read_next_char() if line == 'quit\n': print("are you sure?") if read_next_char() != 'y': continue # ignore the command process_command(line)
Now, assume that you have ported the program to the GUI program, and most of the GUI tools are event-driven. They call a callback function for each user character input. (Replace "GUI" with "XML expat parser", which should be more familiar to you ). In this case, it is very difficult to execute the following function read_next_char. Here are two incompatible functions:
def event_keydown(key): ??def read_next_char(): ?? should wait for the next event_keydown() call
You may consider using a thread to implement this. Greenlets is another optional solution that does not need to associate locks and does not have an on-premise problem. You execute process_commands (), an independent greenlet. Enter a string as follows.
def event_keydown(key): # jump into g_processor, sending it the key g_processor.switch(key)def read_next_char(): # g_self is g_processor in this simple example g_self = greenlet.getcurrent() # jump to the parent (main) greenlet, waiting for the next key next_char = g_self.parent.switch() return next_charg_processor = greenlet(process_commands)g_processor.switch(*args) # input arguments to process_commands()gui.mainloop()
In this example, the execution process is as follows:
- When the read_next_char () function is called as part of g_processor greenlet, when the received input is switched to the upper-level greenlet, the program is restored to the main loop (GUI) for execution.
- When the GUI calls event_keydown (), the program switches to g_processor. This means that the program jumps out, no matter where it is suspended in the greenlet. In this example, switch to read_next_char (), and the key pressed in event_keydown () is returned to read_next_char () as the result of switch ().
It must be noted that the call stack of read_next_char () is retained for both suspension and restoration. In order to restore the data to different locations in prprocess_commands. This makes it possible to control the program logic with a good control flow. We do not need to completely rewrite process_commands () to convert it to a state machine.
Preface
Greenlet is a micro-independent pseudo thread. Consider as a frame stack. The farthest frame (bottom layer) is the initial function you call. The outermost frame (top layer) is pressed in the current greenlet. When you use greenlets, you create a series of such stacks and jump between them for execution. This jump will cause the previous frame to be suspended, and the last frame will be restored from the suspended state. The jump relationship between greenlets is called "switching (switch )".
When you create a greenlet, it will have an initialized empty stack. When you switch to it for the first time, it starts to run a specific function. In this function, other functions may be called to switch from the current greenlet, and so on. When the underlying function is executed and the greenlet stack is empty again, greenlet will die. Greenlet may also be terminated due to an uncaptured exception.
For example:
from greenlet import greenletdef test1(): print 12 gr2.switch() print 34def test2(): print 56 gr1.switch() print 78gr1 = greenlet(test1)gr2 = greenlet(test2)gr1.switch()
- The last line jumps to test1, and then prints 12,
- Jump to test2 and print 56
- Jump back to test1, print 34, test1 completed, and gr1 died. At the same time, the program execution returns to the gr1.switch () call.
- Note that 78 has never been printed.
Parent greenlet
Let's see where the program runs when greenlet dies. Each greenlet has a parent greenlet. The original parent is the greenlet that creates the greenlet (the parent greenlet can be changed at any time ). The parent greenlet is the place where the program continues to run when a greenlet dies. In this way, the program is organized into a tree. The top-level code that is not run in the greenlet created by the user is run in the implicit primary greenlet, which is the root of the number of stacks.
In the preceding example, gr1 and gr2 use the primary greenlet as the parent greenlet. No matter who executes them, the program will return to "main" greenlet.
An uncaptured exception is thrown to the parent greenlet. For example, if test2 () contains a syntax error, it will generate a NameError to kill gr2. This error will jump directly to the primary greenlet. The error stack will display test2 instead of test1. It should be noted that switches is not a call, but a jump directly executed by the program in the parallel "stack iner (stack container, "parent" defines a stack logically located under the current greenlet.
Instantiate object
Greenlet. greenlet is a coroutine type that supports the following operations:
- Greenlet (run = None, parent = None): Creates a new greenlet object (not running yet ). Run is a callable function used for calling. Parent defines the parent greenlet. The default value is the current greenlet.
- Greenlet. getcurrent (): Get the current greenlet (that is, the greenlet that calls this function)
- Greenlet. GreenletExit: This special exception will not be thrown to the parent greenlet, which can be used to kill a single greenlet.
The greenlet type can be quilt-type. Call the run attribute initialized during greenlet creation to execute a greenlet. But for subclass, defining a run method is more meaningful than providing a run parameter to the constructor.
Switch
When the method switch () is called in a greenlet, the switchover between greenlets will occur. Normally, the program execution will jump to the greenlet called by the switch. Or when a greenlet program dies, the execution will jump to the parent greenlet program. When a switchover occurs, an object or an exception will be sent to the target greenlet. This is a convenient way to transmit information in two greenlets. For example:
def test1(x, y): z = gr2.switch(x+y) print zdef test2(u): print u gr1.switch(42)gr1 = greenlet(test1)gr2 = greenlet(test2)gr1.switch("hello", " world")
It has been executed in the same order as in the previous example. It will print "hello world" and "42. The test1 () and test2 () parameters are not given when greenlet is created, but given during the first switchover.
Here we provide clear rules for sending data:
G. switch (* args, ** kwargs): switch to greenlet g to send data. As a special example, if g is not executed, it will start to execute.
For the greenlet that will die. When run () is completed, an object is assigned to the parent greenlet. If greenlet is terminated due to an exception, the exception will be thrown to the parent greenlet (greenlet. GreenletExit exception, which is caught and directly exited to the parent greenlet ).
Except as described in the preceding example, the target greenlet (parent-level greenlet) suspends the call of switch () before receiving the request, and the returned value after execution is complete. In fact, although the call to switch () does not return results immediately, when some other greenlet switches back, results will be returned at a certain point in the future. When a switchover occurs, the program will resume where it was suspended. Switch () itself returns the object that occurs. This means that x = g. switch (y) returns y to g, and then returns an unrelated object from an unrelated greenlet to x variable.
Remind me that any attempt to switch to a dead greenlet will go to the parent level of the dead greenlet, or the parent level, and so on (the final parent level is "main" greenlet, it will never die ).
Greenlets methods and attributes
- G. switch (* args, ** kwargs): switch the program to greenlet g for execution. See the preceding figure.
- G. run: when it starts, the g callback will be executed. When g has started to execute, this attribute will not exist.
- G. parent: parent greenlet. This is an editable attribute, but cannot be written as an endless loop.
- G. gr_frame: the top-level structure, or equal to None.
- G. dead: bool value. When g dies, the value is True.
- Bool (g): bool value. If the returned structure is True, it indicates that g is still active. If it is False, it indicates that it is dead or has not started.
G. throw ([typ, [val, [tb]): switches to g for execution, but immediately throws a given exception. If no parameter is provided, the default exception is greenlet. GreenletExit. As described above, normal exception transfer rules take effect. Calling this method is almost equivalent to the following code:
def raiser(): raise typ, val, tb g_raiser = greenlet(raiser, parent=g) g_raiser.switch()
The difference is that this Code cannot be used for a greenlet. GreenletExit exception, which will not be propagated from g_raiser to g.
Greenlets and python threads
Greenlets can be combined with python threads. In this case, each thread contains an independent "main" greenlet with a child greenlets tree. It is impossible to mix or switch greenlets in different threads.
Greenlets garbage collection Lifecycle
If all the associations of a greenlet have expired (including those from the parent attributes of other greenlets), there is no way to switch back to the greenlet. In this case, the GreenletExit exception will occur. This is the only way greenlet accepts asynchronous execution. Use try: finally: Statement block to clear resources used by the greenlet. This type of attribute supports a programming style. greenlet infinite loops wait for data and execute. When the last link of the greenlet becomes invalid, this loop is automatically terminated.
If greenlet either dies or is restored based on a local association. You only need to capture and ignore the GreenletExit that may cause infinite loops.
Greenlets does not participate in garbage collection. When looping the data in the greenlet framework, the data will not be detected. The reference of other greenlets stored cyclically may cause memory leakage.
Error stack support
When greenlet is used, the standard python error stack and description will not run as expected, because the stack and framework switch happen in the same thread. Using traditional methods to reliably detect greenlet switching is very difficult. Therefore, to improve the support for debugging, error stack, and problem description of greenlet basic code, there are some new methods in the greenlet module:
- Greenlet. gettrace (): returns an existing call stack method, or None.
Greenlet. settrace (callback): sets a new call stack method, and returns the existing method or None. When some events occur, this callback function is called. You can perform signal processing in Yongan.
def callback(event, args): if event == 'switch': origin, target = args # Handle a switch from origin to target. # Note that callback is running in the context of target # greenlet and any exceptions will be passed as if # target.throw() was used instead of a switch. return if event == 'throw': origin, target = args # Handle a throw from origin to target. # Note that callback is running in the context of target # greenlet and any exceptions will replace the original, as # if target.throw() was used with the replacing exception. return
For compatibility, when the event is either a switch or throw, rather than other possible events, the parameter is unwrapped into a tuple. In this way, the API may be extended out of new events similar to sys. settrace.
C api Problems
Greenlets can be generated and maintained by using C/C ++-written extension modules, or applications embedded in python. The greenlet. h header file is provided to demonstrate complete API access to the native python module.
Type
Type namePython namePyGreenletgreenlet. greenlet
Exception
Type namePython namePyExc_GreenletErrorgreenlet.errorPyExc_GreenletExitgreenlet.GreenletExit
Association
- PyGreenlet_Import (): a macro definition, which imports the greenlet module and initializes the c api. It must be called once in every module that uses greenlet c api.
- Int PyGreenlet_Check (PyObject * p): a macro definition. If the parameter is PyGreenlet, true is returned.
- Int PyGreenlet_STARTED (PyGreenlet * g): a macro definition. If greenlet starts, true is returned.
- Int PyGreenlet_ACTIVE (PyGreenlet * g): a macro definition. If greenlet returns true in the activity.
- PyGreenlet * PyGreenlet_GET_PARENT (PyGreenlet * g): a macro definition that returns the parent greenlet in greenlet.
- Int PyGreenlet_SetParent (PyGreenlet * g, PyGreenlet * nparent): sets the parent greenlet. If 0 is returned, the setting is successful.-1 indicates that g is not a valid PyGreenlet pointer, and AttributeError will be thrown.
- PyGreenlet * PyGreenlet_GetCurrent (void): returns the currently active greenlet object.
- PyGreenlet * PyGreenlet_New (PyObject * run, PyObject * parent): use run and parent to create a new greenlet object. These two parameters are optional. If run is NULL. The greenlet creation fails if the switchover starts. If parent is NULL. This parent will be automatically set to the current greenlet.
- PyObject * PyGreenlet_Switch (PyGreenlet * g, PyObject * args, PyObject * kwargs): Switch to greenet g. Args and kwargs are optional and can be NULL. If args is NULL, an empty tuple is sent to the target greenlet g. If kwargs is NULL. No key-value parameter is sent. If the parameter is specified, args should be a tuple and kwargs should be a dict.
- PyObject * PyGreenlet_Throw (PyGreenlet * g, PyObject * typ, PyObject * val, PyObject * tb): Switch to greenlet g and immediately throw the exception specified by the typ parameter (carrying the val value, the call stack object tb is optional and can be NULL.
Indexes and tables