Skip to content
Scroll to top↑

Generator自动执行器

今天也是炫技的一天呢~原意是设法将异步回调变成瀑布执行,然后写出了一个比较奇怪的模式。

Js版本

Promiseasyncawait不过是Generator的语法糖。

js
const autoRun = (gen) => {
  const g = gen();

  const run = () => {
    const result = g.next();
    if (result.done) return;
    result.value(run);
  };

  run();
};

autoRun(function* () {
  yield (next) => setTimeout(() => { // await new Promise((next) => setTimeout(next, 1000))
    console.log(2);
    next();
  }, 1000);
  yield (next) => setTimeout(() => {
    console.log(3);
    next();
  }, 1000);
});

Lua版本:

lua
local co = coroutine 

function setTimeout(callback, timeout)
  local start = os.clock()
  while os.clock() - start <= timeout do end
  callback()
end

local autoRun = function(gen)
  local g = co.create(gen)

  function run() 
    local _,result = co.resume(g)
    if result == nil then return end
    result(run)
  end

  run()
end

autoRun(function() 
  co.yield(function(next) 
    setTimeout(function() 
      print(2)
      next()
    end, 1)
  end)
  co.yield(function(next)
    setTimeout(function() 
      print(3)
      next()
    end, 1)
  end)
end)

Python版本

python
import time

def setTimeout(callback, timeout):
    time.sleep(timeout)
    callback()

def autoRun(gen):
    g = gen()

    def run():
        result = next(g)
        if result != None:
            result(run)

    run()


autoRun(lambda: [
    (yield (lambda n: setTimeout(lambda: [print(2), n()], 1))),
    (yield (lambda n: setTimeout(lambda: [print(3), n()], 1))),
    (yield None)])

Racket版本

用continuation实现。

scheme
#lang racket

(define (setTimeout callback timeout)
  (sleep timeout)
  (callback))
     
(define (autoRun g)
  (let [(next (call/cc (lambda (yield) (g yield))))]
    (if (eq? #f next)
      (void)
      (next))))

(autoRun (lambda (yield)
           (setTimeout (lambda () (println 2) (call/cc (lambda (next) (yield next)))) 1)
           (setTimeout (lambda () (println 3) (call/cc (lambda (next) (yield next)))) 1)
           (yield #f)))

Go版本

用channel模拟yield。这种模拟给了我们一种很微妙的感觉,如果站在整个程序执行流的视角,写入channel和读取channel的两个线程没有任何同时执行的契机,名为多线程,其实还是单线程模型。

go
package main

import (
	"fmt"
	"time"
)

type Yield chan func(func())

func setTimeout(callback func(), timeout time.Duration) {
	time.Sleep(timeout * time.Millisecond)
	callback()
}

func autoRun(gen func(Yield)) {
	yield := make(Yield)
	
	var run func()

	run = func() {
		result := <- yield
		if result != nil {
			result(run)
		}
	}

	go gen(yield)
	run()
}

func main() {
	autoRun(func (yield Yield) {
		yield <- func(next func ()) {
			setTimeout(func () {
				fmt.Println(2)
				next()
			}, 1000)
		}
		yield <- func(next func ()) {
			setTimeout(func () {
				fmt.Println(3)
				next()
			}, 1000)
		}
		yield <- nil
	})
}

Rust版本

也基于channel。对Rust的lifetime还不够了解,见注释。

rust
#![allow(non_snake_case)]
use std::{
    sync::mpsc::{sync_channel, SyncSender},
    thread,
    time::Duration,
};

fn setTimeout(callback: &dyn Fn(), timeout: u64) {
    std::thread::sleep(Duration::from_millis(timeout));
    callback();
}

type Lambda<Param> = Box<dyn Fn(Param) + Send + Sync>;

// Rust does not support recursive lambda function
struct Closure<'a> {
    lambda: &'a dyn Fn(&Closure),
}

// How to convert to SyncSender<Lambda<&Closure>> with appropriate lifetime?
type Yield = SyncSender<Box<dyn Fn(&Closure) + Send + Sync>>;

fn autoRun(gen: Lambda<Yield>) {
    let (tx, rx): (Yield, _) = sync_channel(1);

    let run = Closure {
        lambda: &|this| {
            if let Ok(result) = rx.recv() {
                result(this);
            }
        },
    };

    thread::spawn(move || gen(tx));
    (run.lambda)(&run);
}

pub fn test_yield() {
    autoRun(Box::new(|tx| {
        tx.send(Box::new(|next| {
            setTimeout(
                &|| {
                    println!("2");
                    (next.lambda)(next);
                },
                1000,
            );
        }))
        .unwrap();

        tx.send(Box::new(|next| {
            setTimeout(
                &|| {
                    println!("3");
                    (next.lambda)(next);
                },
                1000,
            );
        }))
        .unwrap();
    }));
}

C++版本

channel不过是信号量的语法糖。

cpp
#include <future>
#include <thread>
#include "example/semaphore.h"

void setTimeout(std::function<void()> const& callback, std::chrono::milliseconds const& timeout) {
  std::this_thread::sleep_for(timeout);
  callback();
}

using Yield = std::function<void(std::function<void()>)>;
// simulate one size channel
class Channel {
 public:
  // write to channel
  friend void operator<<(Channel& chan, Yield const& data) {
    chan.slot.P();
    chan.data = data;
    chan.item.V();
  }

  // read from channel
  friend Yield const& operator>>(Channel& chan, void*) {
    chan.item.P();
    auto data = new Yield(chan.data);
    chan.slot.V();
    return *data;
  }

 private:
  Yield     data;
  Semaphore slot{1};
  Semaphore item{0};
};

void autoRun(std::function<void(Channel&)> const& gen) {
  Channel yield;

  std::function<void()> run = [&]() {
    auto result = yield >> 0;
    if (result == nullptr) return;
    result(run);
  };

  auto p = std::async([&]() { gen(yield); });
  run();
  p.wait();
}

int main() {
  using namespace std;
  using namespace std::chrono_literals;

  autoRun([](Channel& yield) {
    yield << [](std::function<void()> const& next) {
      setTimeout(
          [&next]() {
            cout << 2 << endl;
            next();
          },
          1000ms);
    };

    yield << [](std::function<void()> const& next) {
      setTimeout(
          [&next]() {
            cout << 3 << endl;
            next();
          },
          1000ms);
    };

    yield << nullptr;
  });

  return 0;
}