Threads — Threads and Stackless¶
Stackless is a lightweight threading solution. It works byscheduling its tasklets within the CPU time allocated to the real threadthat thePython® interpreter, and therefore the scheduler running within it, is on.
Stackless does not:
- Magically move tasklets between threads to do some wonderfulload balancing.
- Magically remove the global interpreter lock.
- Solve all your scalability needs out of the box.
But it does allow its functionality to be used flexibly, when youwant to make use of more than one thread.
Tasklets and Threads¶
A tasklet usually has an associated thread. This thread is identified by theattributetasklet.thread_id. A newly created tasklet is alwaysassociated with its creator thread.
The associated thread of a tasklet changes only as the result ofatasklet.bind_thread() call or if the associated threadterminates. In the later case the thread id becomes-1. Applicationcode can bind the tasklet to another thread usingbind_thread().
When a thread dies, only tasklets with a C-state are actively killed.Soft-switched tasklets simply stop. All tasklets bound to the thread willlose their thread-state, which means that theirthread_idwill report as-1. This also includes soft-switched tasklets,which share a C-state.
The reason Stackless kills tasklets with C-state is that not doing socan cause serious leaks when a C-state is not unwound. If Stackless runsin verbose mode (command line option-v orPYTHONVERBOSE),Stackless prints a warning message, if it deallocates a taskletwith a C-state. Stackless cannotkill soft-switched tasklets, because there is no central list of them.Stackless only knows about the hard-switched ones.
Threads that end really should make sure that they finish whatever workertasklets they have going by manually killing (tasklet.kill()) orunbinding (tasklet.bind(None)) them, but that is up to application code.
During interpreter shutdown Stackless kills other daemon threads (non-daemonare already dead at this point), if they execute Python code or switchtasklets. This way Stackless tries to avoid access violations, that mighthappen later after clearing the thread state structures.
A scheduler per thread¶
The operating system thread that thePython® runtime is started in and runs on,is called the main thread. The typical use of Stackless, is to run thescheduler in this thread. But there is nothing that prevents a differentscheduler, and therefore a different set of tasklets, from running in everyPython® thread you care to start.
Example - scheduler per thread:
importthreadingimportstacklessdefsecondary_thread_func():print("THREAD(2): Has",stackless.runcount,"tasklets in its scheduler")defmain_thread_func():print("THREAD(1): Waiting for death of THREAD(2)")whilethread.is_alive():stackless.schedule()print("THREAD(1): Death of THREAD(2) detected")mainThreadTasklet=stackless.tasklet(main_thread_func)()thread=threading.Thread(target=secondary_thread_func)thread.start()stackless.run()
- Output::
- THREAD(2): Has 1 tasklets in its schedulerTHREAD(1): Waiting for death of THREAD(2)THREAD(1): Death of THREAD(2) detected
This example demonstrates that there actually are two independent schedulerspresent, one in each participatingPython® thread. We know that the mainthread has one manually created tasklet running, in addition to its maintasklet which is running the scheduler. If the secondary thread is trulyindependent, then when it runs it should have a tasklet count of1representing its own main tasklet. And this is indeed what we see.
See also:
- The attribute
stackless.threads.- The attribute
tasklet.thread_id.- The method
tasklet.bind_thread().
Channels are thread-safe¶
Whether or not you are running a scheduler on multiple threads, you can stillcommunicate with a thread that is running a scheduler using achannel object.
Example - interthread channel usage:
importthreadingimportstacklesscommandChannel=stackless.channel()defmaster_func():commandChannel.send("ECHO 1")commandChannel.send("ECHO 2")commandChannel.send("ECHO 3")commandChannel.send("QUIT")defslave_func():print("SLAVE STARTING")while1:command=commandChannel.receive()print("SLAVE:",command)ifcommand=="QUIT":breakprint("SLAVE ENDING")defscheduler_run(tasklet_func):t=stackless.tasklet(tasklet_func)()whilet.alive:stackless.run()thread=threading.Thread(target=scheduler_run,args=(master_func,))thread.start()scheduler_run(slave_func)
Output:
SLAVESTARTINGSLAVE:ECHO1SLAVE:ECHO2SLAVE:ECHO3SLAVE:QUITSLAVEENDING
This example runsslave_func as a tasklet on the main thread, andmaster_func as a tasklet on a secondary thread that is manually created.The idea is that the master thread tells the slave thread what to do, withaQUIT message meaning that it should exit.
Note
The reason the scheduler is repeatedly run in a loop, is because when ascheduler has no remaining tasklets scheduled within it, it will exit.As there is only one tasklet in each thread, as each channel operation inthe thread blocks the calling tasklet, the scheduler will exit. Linkinghow long the scheduler is driven to the lifetime of all tasklets that ithandles, ensures correct behaviour.
