Crate greenExperimental[stability] [-]  [+] [src]

The "green scheduling" library

This library provides M:N threading for rust programs. Internally this has the implementation of a green scheduler along with context switching and a stack-allocation strategy. This can be optionally linked in to rust programs in order to provide M:N functionality inside of 1:1 programs.

Architecture

An M:N scheduling library implies that there are N OS thread upon which M "green threads" are multiplexed. In other words, a set of green threads are all run inside a pool of OS threads.

With this design, you can achieve concurrency by spawning many green threads, and you can achieve parallelism by running the green threads simultaneously on multiple OS threads. Each OS thread is a candidate for being scheduled on a different core (the source of parallelism), and then all of the green threads cooperatively schedule amongst one another (the source of concurrency).

Schedulers

In order to coordinate among green threads, each OS thread is primarily running something which we call a Scheduler. Whenever a reference to a Scheduler is made, it is synonymous to referencing one OS thread. Each scheduler is bound to one and exactly one OS thread, and the thread that it is bound to never changes.

Each scheduler is connected to a pool of other schedulers (a SchedPool) which is the thread pool term from above. A pool of schedulers all share the work that they create. Furthermore, whenever a green thread is created (also synonymously referred to as a green task), it is associated with a SchedPool forevermore. A green thread cannot leave its scheduler pool.

Schedulers can have at most one green thread running on them at a time. When a scheduler is asleep on its event loop, there are no green tasks running on the OS thread or the scheduler. The term "context switch" is used for when the running green thread is swapped out, but this simply changes the one green thread which is running on the scheduler.

Green Threads

A green thread can largely be summarized by a stack and a register context. Whenever a green thread is spawned, it allocates a stack, and then prepares a register context for execution. The green task may be executed across multiple OS threads, but it will always use the same stack and it will carry its register context across OS threads.

Each green thread is cooperatively scheduled with other green threads. Primarily, this means that there is no pre-emption of a green thread. The major consequence of this design is that a green thread stuck in an infinite loop will prevent all other green threads from running on that particular scheduler.

Scheduling events for green threads occur on communication and I/O boundaries. For example, if a green task blocks waiting for a message on a channel some other green thread can now run on the scheduler. This also has the consequence that until a green thread performs any form of scheduling event, it will be running on the same OS thread (unconditionally).

Work Stealing

With a pool of schedulers, a new green task has a number of options when deciding where to run initially. The current implementation uses a concept called work stealing in order to spread out work among schedulers.

In a work-stealing model, each scheduler maintains a local queue of tasks to run, and this queue is stolen from by other schedulers. Implementation-wise, work stealing has some hairy parts, but from a user-perspective, work stealing simply implies what with M green threads and N schedulers where M > N it is very likely that all schedulers will be busy executing work.

Considerations when using libgreen

An M:N runtime has both pros and cons, and there is no one answer as to whether M:N or 1:1 is appropriate to use. As always, there are many advantages and disadvantages between the two. Regardless of the workload, however, there are some aspects of using green thread which you should be aware of:

Note that these concerns do not mean that operating with native code is a lost cause. These are simply just concerns which should be considered when invoking native code.

Starting with libgreen

extern crate green; #[start] fn start(argc: int, argv: *const *const u8) -> int { green::start(argc, argv, green::basic::event_loop, main) } fn main() { // this code is running in a pool of schedulers }
extern crate green;

#[start]
fn start(argc: int, argv: *const *const u8) -> int {
    green::start(argc, argv, green::basic::event_loop, main)
}

fn main() {
    // this code is running in a pool of schedulers
}

Note: This main function in this example does not have I/O support. The basic event loop does not provide any support

Using a scheduler pool

This library adds a GreenTaskBuilder trait that extends the methods available on std::task::TaskBuilder to allow spawning a green task, possibly pinned to a particular scheduler thread:

extern crate green; fn main() { use std::task::TaskBuilder; use green::{SchedPool, PoolConfig, GreenTaskBuilder}; let mut config = PoolConfig::new(); let mut pool = SchedPool::new(config); // Spawn tasks into the pool of schedulers TaskBuilder::new().green(&mut pool).spawn(proc() { // this code is running inside the pool of schedulers spawn(proc() { // this code is also running inside the same scheduler pool }); }); // Dynamically add a new scheduler to the scheduler pool. This adds another // OS thread that green threads can be multiplexed on to. let mut handle = pool.spawn_sched(); // Pin a task to the spawned scheduler TaskBuilder::new().green_pinned(&mut pool, &mut handle).spawn(proc() { /* ... */ }); // Handles keep schedulers alive, so be sure to drop all handles before // destroying the sched pool drop(handle); // Required to shut down this scheduler pool. // The task will panic if `shutdown` is not called. pool.shutdown(); }
extern crate green;

use std::task::TaskBuilder;
use green::{SchedPool, PoolConfig, GreenTaskBuilder};

let mut config = PoolConfig::new();

let mut pool = SchedPool::new(config);

// Spawn tasks into the pool of schedulers
TaskBuilder::new().green(&mut pool).spawn(proc() {
    // this code is running inside the pool of schedulers

    spawn(proc() {
        // this code is also running inside the same scheduler pool
    });
});

// Dynamically add a new scheduler to the scheduler pool. This adds another
// OS thread that green threads can be multiplexed on to.
let mut handle = pool.spawn_sched();

// Pin a task to the spawned scheduler
TaskBuilder::new().green_pinned(&mut pool, &mut handle).spawn(proc() {
    /* ... */
});

// Handles keep schedulers alive, so be sure to drop all handles before
// destroying the sched pool
drop(handle);

// Required to shut down this scheduler pool.
// The task will panic if `shutdown` is not called.
pool.shutdown();

Modules

basic

This is a basic event loop implementation not meant for any "real purposes" other than testing the scheduler and proving that it's possible to have a pluggable event loop.

context
coroutine
sched
sleeper_list

Maintains a shared list of sleeping schedulers. Schedulers use this to wake each other up.

stack
task

The Green Task implementation

Structs

GreenSpawner

A spawner for green tasks

PoolConfig

Configuration of how an M:N pool of schedulers is spawned.

SchedPool

A structure representing a handle to a pool of schedulers. This handle is used to keep the pool alive and also reap the status from the pool.

TaskState

This is an internal state shared among a pool of schedulers. This is used to keep track of how many tasks are currently running in the pool and then sending on a channel once the entire pool has been drained of all tasks.

Traits

GreenTaskBuilder

An extension trait adding green configuration methods to TaskBuilder.

Functions

run

Execute the main function in a pool of M:N schedulers.

start

Set up a default runtime configuration, given compiler-supplied arguments.