Code Monkey home page Code Monkey logo

Comments (13)

ChuanqiXu9 avatar ChuanqiXu9 commented on June 18, 2024

感觉对 Executor 的修改还是以通用/兼容的方式去更改比较好。例如在 Executor 增加 Lazy_locals 的接口看上去就没有直接在 Executor 中增加优先级相关的接口好。

这样可以在用户自定义的 awaiter 中写:

await_suspend() {
    void *lazy_local = get lazy locals
    user_type *u = convert lazy_local to user_type
    priority_type p = get priority from u
    executor->schedule(resume_fn, p);
}

这样看上去会更好一些

from async_simple.

chloro-pn avatar chloro-pn commented on June 18, 2024

首先肯定是要满足前向兼容的,这里优先级只是一个例子,我的意思是我们可以直接加一个schedule(func, void*)的接口,默认实现就是调用schedule(func),然后把现在所有调用schedule的地方重构,检测lazy_local是否为nullptr,是的话调用schedule(func),不是的话从promise拿到lazy_local,然后调用schedule(func, void*)
这样,只有用户同时自定义了schedule(func, void*)并且提供了lazy_local,才会调用新的接口,其他情况都是走schedule(func),是完全兼容的。
这样的好处是async_simple完全不需要感知Executor的设计,只需要负责传递void*指针,由实现者把它转为优先级或者id或者其他更复杂的类型来指导调度。
其实我们缺少的是一个将用户信息透传给Executor的设计,async_simple只需要做中间层传递信息就行。

from async_simple.

ChuanqiXu9 avatar ChuanqiXu9 commented on June 18, 2024

schedule(func, void*) 这种接口的语义太奇怪了,void* 基本没有任何语义。多态接口的意义是可以在不同的实现间迁移,但 schedule(func, void*) 明显不满足这种要求。

async_simple完全不需要感知Executor的设计

异步库可以和 Executor 的具体实现解藕,但肯定是需要感知 Executor 的设计接口的。

from async_simple.

chloro-pn avatar chloro-pn commented on June 18, 2024

emmm是这样的,这种接口擦除了太多信息,好处是非常灵活,可扩展性强,坏处是由于信息擦除导致不能提供一个完备的抽象层,实际上将上层应用和Executor耦合起来了。
那就先加一个优先级的接口吧,支持固定计算资源的条件下跑一些不同优先级的任务场景。

from async_simple.

ChuanqiXu9 avatar ChuanqiXu9 commented on June 18, 2024

是的,现在看编程的主流趋势都是 prefer explicit than implicit 的。Implicit 的很多东西都被看成 hack 了,感觉不是很受欢迎。

from async_simple.

chloro-pn avatar chloro-pn commented on June 18, 2024

感觉对 Executor 的修改还是以通用/兼容的方式去更改比较好。例如在 Executor 增加 Lazy_locals 的接口看上去就没有直接在 Executor 中增加优先级相关的接口好。

这样可以在用户自定义的 awaiter 中写:

await_suspend() {
    void *lazy_local = get lazy locals
    user_type *u = convert lazy_local to user_type
    priority_type p = get priority from u
    executor->schedule(resume_fn, p);
}

这样看上去会更好一些

优先级应该和lazy_local类似,是绑定到RescheduleLazy的属性吧,否则resume_fn的优先级只能保留到下次挂起,需要用户每次在suspend中显式指定。

from async_simple.

ChuanqiXu9 avatar ChuanqiXu9 commented on June 18, 2024

感觉对 Executor 的修改还是以通用/兼容的方式去更改比较好。例如在 Executor 增加 Lazy_locals 的接口看上去就没有直接在 Executor 中增加优先级相关的接口好。
这样可以在用户自定义的 awaiter 中写:

await_suspend() {
    void *lazy_local = get lazy locals
    user_type *u = convert lazy_local to user_type
    priority_type p = get priority from u
    executor->schedule(resume_fn, p);
}

这样看上去会更好一些

优先级应该和lazy_local类似,是绑定到RescheduleLazy的属性吧,否则resume_fn的优先级只能保留到下次挂起,需要用户每次在suspend中显式指定。

这是什么意思呢?没太看明白

from async_simple.

chloro-pn avatar chloro-pn commented on June 18, 2024

额,等我先实现一版再讨论吧,有点抽象。

from async_simple.

chloro-pn avatar chloro-pn commented on June 18, 2024

感觉对 Executor 的修改还是以通用/兼容的方式去更改比较好。例如在 Executor 增加 Lazy_locals 的接口看上去就没有直接在 Executor 中增加优先级相关的接口好。
这样可以在用户自定义的 awaiter 中写:

await_suspend() {
    void *lazy_local = get lazy locals
    user_type *u = convert lazy_local to user_type
    priority_type p = get priority from u
    executor->schedule(resume_fn, p);
}

这样看上去会更好一些

优先级应该和lazy_local类似,是绑定到RescheduleLazy的属性吧,否则resume_fn的优先级只能保留到下次挂起,需要用户每次在suspend中显式指定。

这是什么意思呢?没太看明白

我的意思是这种由用户在suspend中指定优先级调度的方式,优先级只能保留到下一次被挂起,例如:

Lazy<void> test() {
    // 用户在awaiter1中指定了优先级调度
    co_await awaiter1();
    // ...
    // awaiter2中是默认的schedule调度,这之后优先级就丢失了。
    // awaiter2可能是Mutex、Dispatch等
    co_await awaiter2();
    // ...
}

我的这个实现是将优先级(int8_t类型,默认为0)绑定到RescheduleLazy,启动RescheduleLazy可以为其指定一个初始优先级。
并且提供一个UpdatePriority的接口来更新优先级,这种更新可以是延迟的(只更新不重新调度),也可以是立即的(更新并立即重新调度)。
Executor新加一个scheduleWithPriority接口,默认实现就是调用schedule,这样保证前向兼容。然后把async_simple内部使用的schedule替换为scheduleWithPriority,这样Lazy的优先级信息就不会丢失。

#336

from async_simple.

ChuanqiXu9 avatar ChuanqiXu9 commented on June 18, 2024

我的意思是这种由用户在suspend中指定优先级调度的方式,优先级只能保留到下一次被挂起,例如:

Lazy<void> test() {
    // 用户在awaiter1中指定了优先级调度
    co_await awaiter1();
    // ...
    // awaiter2中是默认的schedule调度,这之后优先级就丢失了。
    // awaiter2可能是Mutex、Dispatch等
    co_await awaiter2();
    // ...
}

我感觉这种场景本来就不应该支持吧?callee 的 context 改变影响 caller 的 context 的改变,这个感觉比较奇怪啊。因为在 async_simple 的层面,整体的设计都是 top-down 的:由用户指定调度器,之后在用户给定的 await_suspend 中进行处理(一般是 IO)。从另一个角度来说,可以将同一个执行流下的若干个 Lazy 看作是一个任务(例如,一个查询)。这也是我们设置 Executor 和 lazy_local 的原因。回到 Priority 中,我们可以接受一个任务有与其他任务不同的优先级,但一个任务的不同部分有着不同的优先级就比较怪了。

我的这个实现是将优先级(int8_t类型,默认为0)绑定到RescheduleLazy,启动RescheduleLazy可以为其指定一个初始优先级。
并且提供一个UpdatePriority的接口来更新优先级,这种更新可以是延迟的(只更新不重新调度),也可以是立即的(更新并立即重新调度)。
Executor新加一个scheduleWithPriority接口,默认实现就是调用schedule,这样保证前向兼容。然后把async_simple内部使用的schedule替换为scheduleWithPriority,这样Lazy的优先级信息就不会丢失。

我的主要 concern 是在这个思路里 Priority 并不是一个具有特殊性的属性。即今天我们想要优先级最高特性,那么明天该怎么保证我们不会想要其他特性呢?例如 Promt。在这种情况下,现在的更改方式看上去不 scalable。


另外一个思路是将现在 Lazy.via(Executor) 中的 Executor 看作是一个调度器适配器,而不是一个单独的调度器。不同的调度器适配器可以对应同一个调度器的不同调度策略。

所以可以有:

Lazy<int> task():

ThreadPool pool;
auto DefaultExecutor = pool.getExecutor();
auto Priority10Executor = pool.getExecutorWithPriority(10);

task.via(Priority10Executor).start();

...

Lazy<int> task() {
    // ...
    co_await dispatch{DefaultExecutor};
}

这样就可以在兼容 async_simple 现有设计的情况下,去进行各种调度器的扩展了。

from async_simple.

chloro-pn avatar chloro-pn commented on June 18, 2024

我的意思是这种由用户在suspend中指定优先级调度的方式,优先级只能保留到下一次被挂起,例如:

Lazy<void> test() {
    // 用户在awaiter1中指定了优先级调度
    co_await awaiter1();
    // ...
    // awaiter2中是默认的schedule调度,这之后优先级就丢失了。
    // awaiter2可能是Mutex、Dispatch等
    co_await awaiter2();
    // ...
}

我感觉这种场景本来就不应该支持吧?callee 的 context 改变影响 caller 的 context 的改变,这个感觉比较奇怪啊。因为在 async_simple 的层面,整体的设计都是 top-down 的:由用户指定调度器,之后在用户给定的 await_suspend 中进行处理(一般是 IO)。从另一个角度来说,可以将同一个执行流下的若干个 Lazy 看作是一个任务(例如,一个查询)。这也是我们设置 Executor 和 lazy_local 的原因。回到 Priority 中,我们可以接受一个任务有与其他任务不同的优先级,但一个任务的不同部分有着不同的优先级就比较怪了。

我的这个实现是将优先级(int8_t类型,默认为0)绑定到RescheduleLazy,启动RescheduleLazy可以为其指定一个初始优先级。
并且提供一个UpdatePriority的接口来更新优先级,这种更新可以是延迟的(只更新不重新调度),也可以是立即的(更新并立即重新调度)。
Executor新加一个scheduleWithPriority接口,默认实现就是调用schedule,这样保证前向兼容。然后把async_simple内部使用的schedule替换为scheduleWithPriority,这样Lazy的优先级信息就不会丢失。

我的主要 concern 是在这个思路里 Priority 并不是一个具有特殊性的属性。即今天我们想要优先级最高特性,那么明天该怎么保证我们不会想要其他特性呢?例如 Promt。在这种情况下,现在的更改方式看上去不 scalable。

另外一个思路是将现在 Lazy.via(Executor) 中的 Executor 看作是一个调度器适配器,而不是一个单独的调度器。不同的调度器适配器可以对应同一个调度器的不同调度策略。

所以可以有:

Lazy<int> task():

ThreadPool pool;
auto DefaultExecutor = pool.getExecutor();
auto Priority10Executor = pool.getExecutorWithPriority(10);

task.via(Priority10Executor).start();

...

Lazy<int> task() {
    // ...
    co_await dispatch{DefaultExecutor};
}

这样就可以在兼容 async_simple 现有设计的情况下,去进行各种调度器的扩展了。

额,我的意思也是这种使用方式不合理,因为你举得这个例子我以为你是这么想的。由RescheduleLazy启动的一系列Lazy可以被看做一个“任务”(或者叫其他什么名字不重要),这些任务具有一些共有的属性(Executor、lazy_local、priority等),这一点我们是达成共识的。


回到 Priority 中,我们可以接受一个任务有与其他任务不同的优先级,但一个任务的不同部分有着不同的优先级就比较怪了。

关于这个问题,其实是为了支持计算密集型任务动态调整优先级(比如周期性的根据执行情况调整任务的优先级,使得多个任务尽量公平的获得cpu),貌似async_simple目前的设计主要还是用来跑io型任务偏多。对于计算密集型任务而言,现在Executor拿到的信息太少了,甚至无法区分哪些func属于同一个或不属于同一个任务,因此一些调度算法就无法使用。(无栈协程的使用具有传染性,并不是专门用协程来跑计算密集型任务)


我的主要 concern 是在这个思路里 Priority 并不是一个具有特殊性的属性。即今天我们想要优先级最高特性,那么明天该怎么保证我们不会想要其他特性呢?例如 Promt。在这种情况下,现在的更改方式看上去不 scalable。

额,这个主要是考虑到之前的设计和兼容性问题,现在的改动确实是这样,也许需要一个更好的实现。


另外一个思路是将现在 Lazy.via(Executor) 中的 Executor 看作是一个调度器适配器,而不是一个单独的调度器。不同的调度器适配器可以对应同一个调度器的不同调度策略。

这样理论上是可行的,但是需要在调度之前提前定义好调度器适配器,适配器的优先级是静态的,然后通过lazy_local传递给Lazy调用栈使用。有点不灵活但是应该也可以用。

from async_simple.

chloro-pn avatar chloro-pn commented on June 18, 2024

emm这个pr我先关了,这种特殊的使用情况可能还是需要fork一份自己定制化,设计总有取舍,确实比较难搞。

from async_simple.

ChuanqiXu9 avatar ChuanqiXu9 commented on June 18, 2024

关于这个问题,其实是为了支持计算密集型任务动态调整优先级(比如周期性的根据执行情况调整任务的优先级,使得多个任务尽量公平的获得cpu),貌似async_simple目前的设计主要还是用来跑io型任务偏多。对于计算密集型任务而言,现在Executor拿到的信息太少了,甚至无法区分哪些func属于同一个或不属于同一个任务,因此一些调度算法就无法使用。(无栈协程的使用具有传染性,并不是专门用协程来跑计算密集型任务)

这个涉及到调度器的实现了,我们生产用的调度算法是将 scheduler 的 task 分到多个由调度器定义的 Execution Context 中,然后对这些 Execution Context 进行动态调度,包括优先级调整、Stealing 等等。

这样理论上是可行的,但是需要在调度之前提前定义好调度器适配器,适配器的优先级是静态的,然后通过lazy_local传递给Lazy调用栈使用。有点不灵活但是应该也可以用。

虽然没实操过,但我感觉还行吧。因为虽然调度器适配器的定义是静态的,但调度器适配器的选择则可以是动态的。感觉要求提前显式将调度策略对应的适配器声明出来并不是坏事,哪怕在策略种类比较多且复杂的情况下,通过一些元编程技术应该都是可以比较好解决的。

from async_simple.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.