Comments (13)
感觉对 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.
首先肯定是要满足前向兼容的,这里优先级只是一个例子,我的意思是我们可以直接加一个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.
但 schedule(func, void*)
这种接口的语义太奇怪了,void* 基本没有任何语义。多态接口的意义是可以在不同的实现间迁移,但 schedule(func, void*)
明显不满足这种要求。
async_simple完全不需要感知Executor的设计
异步库可以和 Executor 的具体实现解藕,但肯定是需要感知 Executor 的设计接口的。
from async_simple.
emmm是这样的,这种接口擦除了太多信息,好处是非常灵活,可扩展性强,坏处是由于信息擦除导致不能提供一个完备的抽象层,实际上将上层应用和Executor耦合起来了。
那就先加一个优先级的接口吧,支持固定计算资源的条件下跑一些不同优先级的任务场景。
from async_simple.
是的,现在看编程的主流趋势都是 prefer explicit than implicit 的。Implicit 的很多东西都被看成 hack 了,感觉不是很受欢迎。
from async_simple.
感觉对 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.
感觉对 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.
额,等我先实现一版再讨论吧,有点抽象。
from async_simple.
感觉对 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的优先级信息就不会丢失。
from async_simple.
我的意思是这种由用户在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.
我的意思是这种由用户在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.
emm这个pr我先关了,这种特殊的使用情况可能还是需要fork一份自己定制化,设计总有取舍,确实比较难搞。
from async_simple.
关于这个问题,其实是为了支持计算密集型任务动态调整优先级(比如周期性的根据执行情况调整任务的优先级,使得多个任务尽量公平的获得cpu),貌似async_simple目前的设计主要还是用来跑io型任务偏多。对于计算密集型任务而言,现在Executor拿到的信息太少了,甚至无法区分哪些func属于同一个或不属于同一个任务,因此一些调度算法就无法使用。(无栈协程的使用具有传染性,并不是专门用协程来跑计算密集型任务)
这个涉及到调度器的实现了,我们生产用的调度算法是将 scheduler 的 task 分到多个由调度器定义的 Execution Context 中,然后对这些 Execution Context 进行动态调度,包括优先级调整、Stealing 等等。
这样理论上是可行的,但是需要在调度之前提前定义好调度器适配器,适配器的优先级是静态的,然后通过lazy_local传递给Lazy调用栈使用。有点不灵活但是应该也可以用。
虽然没实操过,但我感觉还行吧。因为虽然调度器适配器的定义是静态的,但调度器适配器的选择则可以是动态的。感觉要求提前显式将调度策略对应的适配器声明出来并不是坏事,哪怕在策略种类比较多且复杂的情况下,通过一些元编程技术应该都是可以比较好解决的。
from async_simple.
Related Issues (20)
- Deploy static content to Pages task is getting failure HOT 3
- a bug in specifying executors for Future HOT 11
- What about add a new feature lazy_local HOT 6
- Try.h报错信息拼写错误
- co_await async_simple::coro::sleep() question HOT 4
- how to resume a Yield coro HOT 2
- 可否提供一个客户端异步http请求的例子? HOT 11
- ConditionVariable::notifyOne 可能miss唤醒,
- 請問如何實現類似epoll的模型? HOT 4
- There might be better to use like `asyncio.gather` in example of `StacklessCoroutineAndFuture.md` HOT 1
- coro::ConditionVariable is not aware of Executor HOT 3
- A question about Mutex implementation HOT 3
- GTEST_INCLUDE_DIR-NOTFOUND HOT 2
- how to adapt to callback style API HOT 3
- Bug Report: Extra Increment on Generator::iterator After Completion Causes Crash Using Clang15 HOT 6
- What are the best practices for cancellation? HOT 1
- 相比于老式的异步编程, switch延时如何? HOT 2
- Mutex unlock这里为啥是原地resume呀 HOT 2
- 怎么在collectAny后再继续等待其他RescheduleLazy执行完毕 HOT 7
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from async_simple.