Theory and Design of PL (CS 538)
April 22, 2020
Stay tuned for more detailed instructions
Not all material is equally important!
Q: What if we need more concurrency?
Use a small number of threads to handle a lot of concurrent tasks
# Generator producing 0, 1, ..., n-1 one at a time
def firstn(n):
num = 0
while num < n:
yield num # return num to caller, suspend execution
num += 1 # resume here next time generator called
gen = firstn(100); # initialize generator
res0 = next(gen); # 0
res1 = next(gen); # 1
res2 = next(gen); # 2
res3 = next(gen); # 3
var q := new queue
coroutine produce
loop
while q is not full
create some new items
add the items to q
yield to consume
coroutine consume
loop
while q is not empty
remove some items from q
use the items
yield to produce
Potential for a lot more concurrency than one task per thread!
Generally: only use it if you need it!
food = order_food(); // yield until order ready
drink = order_drink(); // yield until drink order ready
if food == burger
make_burger(); // yield until burger ready
else
make_pizza(); // yield until pizza ready
if drink == milkshake
make_milkshake(); // yield until milkshake ready
else
make_iced_tea(); // yield until tea ready
wash_dishes(); // yield until dishes ready
enum Food { Burger, Pizza }
enum Drink { Milkshake, Tea }
enum WaiterState {
Start,
WaitingForFood,
WaitingForDrink(Food), // remember food order
WaitingForBurger(Drink), // remember drink order
WaitingForPizza(Drink), // remember drink order
WaitingForMilkshake,
WaitingForTea,
WaitingForDishes,
Done,
}
enum WaiterState {
Start,
WaitFood1_WaitFood2,
WaitFood1_WaitDrink2(Food), // food for 2
WaitFood1_WaitBurger2(Drink), // drink for 2
WaitFood1_WaitPizza2(Drink), // drink for 2
WaitFood1_WaitMilkshake2,
WaitFood1_WaitTea2,
WaitFood1_WaitDishes2,
WaitDrink1_WaitFood2(Food), // food for 1
WaitDrink1_WaitDrink2(Food, Food), // food for 1 and 2
WaitDrink1_WaitBurger2(Food, Drink), // food for 1, drink for 2
WaitDrink1_WaitPizza2(Food, Drink), // food for 1, drink for 2
WaitDrink1_WaitMilkshake2(Food), // food for 1
WaitDrink1_WaitTea2(Food), // food for 1
WaitDrink1_WaitDishes2(Food), // food for 1
// ... like another 35 states ...
}
poll
might advance state machine
Ready
: state machine doneNotReady
: did some work, not done yetimpl Future for ThenState<Fut1, Fut2, F, T> {
type Output = T;
fn poll(&mut self) -> Poll<T> {
match self {
Start(fut1, f) => {
if let Ready(res1) = fut1.poll() {
*self = WaitingSecond(f(res1)); return NotReady
}
}
WaitingSecond(fut2) => {
if let Ready(res2) = fut2.poll() {
*self = Done(res2); return NotReady
}
}
Done(res) => return Ready(res)
}
}
}
fn then<Fut1, Fut2, F, T>(first: Fut1, f: F)
-> ThenState<Fut1, Fut2, F, T>
where
Fut1: Future<Output = S>,
F: FnOnce(S) -> Fut2,
Fut2: Future<Output = T>,
impl Future for JoinState<Fut1, Fut2, T1, T2> {
type Output = (T1, T2);
fn poll(&mut self) -> Poll<T> {
match self {
Start(fut1, fut2) => {
match (fut1.poll(), fut2.poll()) {
(Ready(res1), Ready(res2)) => *self = Done(res1, res2),
(Ready(res1), NotReady) => *self = WaitingSecond(res1, fut2),
(NotReady, Ready(res2)) => *self = WaitingFirst(fut1, res2),
_ => (),
}; return NotReady
}
WaitingFirst(fut1, res2) => {
if let Ready(res2) = fut2.poll() {
*self = Done(res1, res2); return NotReady
}
}
WaitingSecond(res1, fut2) => {
if let Ready(res1) = fut1.poll() {
*self = Done(res1, res2); return NotReady
}
}
Done(res1, res2) => return Ready(res1, res2)
}
}
}
fn join<Fut1, Fut2, T1, T2>(fst: Fut1, snd: Fut2) -> JoinState<Fut1, Fut2, T1, T2>
where
Fut1: Future<Output = T1>,
Fut2: Future<Output = T2>,
{ Start(fst, snd) }
// impl Future for FoodFuture { type Output = Food; ... }
let mut get_food_order: FoodFuture = ...;
// impl Future for DrinkFuture { type Output = (Drink, Food); ... }
// Keep track of food order while getting drink
let mut get_drink_with_ord: Fn(Food) -> DrinkFuture = ...;
// impl Future for BurgerFuture { type Output = Drink; ... }
// Keep track of drink order while making burger
let mut make_burger_with_drink: Fn(Drink) -> BurgerFuture = ...;
let mut make_pizza_with_drink: Fn(Drink) -> PizzaFuture = ...;
let mut make_milkshake: MilkshakeFuture = ...;
let mut make_tea: TeaFuture = ...;
let mut wash_dishes: DishesFuture = ...;
let mut cust1 = get_food_order
.then(|ord| get_drink_with_order(ord))
.then(|(drink, ord)| {
if ord == Burger { make_burger_with_drink(drink) }
else { make_pizza_with_drink(drink) }
})
.then(|drink| {
if drink == Milkshake { make_milkshake }
else { make_tea }
});
let mut cust2 = // ... same as cust1 ...
let mut waiter = future::join(cust1, cust2).then(|| wash_dishes);
let mut cust_food1 = get_food_order
.then(|ord| {
if ord == Burger { make_burger }
else { make_pizza }
})
let mut cust_drink1 = get_drink
.then(|drink| {
if drink == Milkshake { make_milkshake }
else { make_tea }
});
let mut cust_food2 = // ... same as cust_food1 ...
let mut cust_drink2 = // ... same as cust_drink1 ...
let mut waiter_future = future::join4(
cust_food1, cust_food2, cust_drink1, cust_drink2)
.then(|| wash_dishes);
poll
FutureExt
for more combinatorsTryFutureExt
for working with Result futures