看了 kotlin 的协程实现,感觉就是线程调度库,换句话说就是伪协程,那和 Rxjava 这种东西的区别在哪呢?是切换效率更高,写起来更简洁吗?还是就因为是官方钦定的
1
LosLord 2020-03-20 23:40:55 +08:00
为了让 Callback 看起来不反人类
|
2
aguesuka 2020-03-21 00:06:07 +08:00
java:
read(result-> dosomething()); kotlin: val result = read(); doSomething(); java 没法在语言级别做到这样写异步代码 |
3
winterbells 2020-03-21 00:15:44 +08:00 via Android
|
4
xcstream 2020-03-21 00:57:42 +08:00
代码上减少嵌套
Kotlin 还可以转译成非 java 的语言 |
5
Tyanboot 2020-03-21 03:11:18 +08:00
如果这个算伪协程的话, 那真协程应该是什么样子, 或者说具有什么特性才算是真协程呢.
|
6
abcbuzhiming 2020-03-21 08:00:54 +08:00 2
@Tyanboot 协程的定义是“用户级别的多线程”,而传统的“多线程”是有内核态参与的系统级别实现的多线程,这种实现的线程的建立成本不低,因为内核资源宝贵,并且切换线程的时候有所谓的线程上下文切换开销,用户态内核态切换开销。而协程只在用户态,所以新建协程的成本很低,一台机器上可以建远远比线程上限数量高的多的协程,并且因为只在用户态,协程的切换成本也很低。虽然协程的任务最终还是交给系统线程完成的,但是协程的发明是语言技术进步而对内核技术的一次瘦身,多线程技术有内核态参与这个问题本质上是早期的语言性能不够,为了性能,让内核具备了多线程调度的能力,提供了一种通用的多线程编程模型,且性能不错,但是随着时间语言技术开始进步,慢慢的就有人觉得内核参与的线程成本太高了,而用户态这边的硬件和软件资源突飞猛进,有了超越内核态的可能性,于是就有人希望把多线程再次移动回用户态,这本质是技术发展的一种历史轮回
|
7
PDX 2020-03-21 08:40:59 +08:00
怎么看出是“伪协程”的??
|
8
reus 2020-03-21 08:52:41 +08:00 2
@abcbuzhiming 你对协程的定义是错误的,协程就是“协作式调度的过程”,和它相对的是“抢占式调度的过程”,和用户态内核态没有关系。协作式调度是早于抢占式调度出现的,不存在什么“协程的发明”。基于时钟中断的操作系统线程,在内核看来,也是协作式的,只不过在用户看来是抢占式。
用户态线程就是用户态线程,不要和协程混为一谈。 |
9
micean 2020-03-21 09:28:28 +08:00
1. 让代码看得更舒服
2. 异常处理更舒服 3. 逻辑处理更舒服 java 的 vertx 这么写异步 ``` Promise.promise(p -> 异步 A(p) ).compose(结果 A -> { if(逻辑 A){ return Promise.promise(p -> 异步 B(结果 A)) }else{ return Promise.promise(p -> 异步 C(结果 A)).compose(结果 B -> Promise.promise(p -> 异步 D(结果 B))) } }).map(结果 C -> 结果 D ).setHandler(最终结果 -> { if(没有异常){ 返回结果 }else{ 处理异常 } }) ``` 同步代码这么写 ``` try{ 结果 A = 异步 A() if(逻辑 A){ 结果 C = 异步 B(结果 A) }else{ 结果 B = 异步 C(结果 A) 结果 C = 异步 D(结果 B) } return (结果 D)结果 C }catch(e){ 处理异常 } ``` |
10
wancaibida 2020-03-21 09:35:15 +08:00 via iPhone
为了以更直观的方式写异步
|
11
hyyou2010 2020-03-21 09:51:54 +08:00
我没有读过协程的内部实现,我的理解是:启动协程时可以新启线程,但是协程切换可以在同一个线程内,且仅在用户态完成,这就算真协程,非常高效,这就比需要切换线程的方案强。
|
12
crella 2020-03-21 09:54:10 +08:00
如果我理解得没有错的话,ruby 的 Fiber 也是伪协程,只是方便不同函数的让出 /切换。在不支持 fork 的系统上,支持并发的只有 Thread.new 。
|
13
newmlp 2020-03-21 10:05:56 +08:00
协程就是在用户空间实现的线程啊,不然你觉得协程是啥
|
14
sukaidev 2020-03-21 10:19:43 +08:00
kotlin 既然是 JVM 语言 自然跳脱不出 JVM
协程就是为了能够像平时写同步代码一样写异步代码 如果说 rxjava 是方便的线程切换 那协程就是感觉不到线程在切换 甚至不需要切换线程同样做到了“异步” |
15
janxin 2020-03-21 10:41:02 +08:00 via iPhone
自然是为了不反人类…这是协程存在的意义之一
|
16
codehz 2020-03-21 11:48:23 +08:00 via Android
协程的意义是可以以一致的方法写同步或者异步的调用,而不需要大规模改变写法(比如变成一堆回调)
准确说它并没有提升性能的意思在里面,也不是什么银弹,不可能把原本做不到异步的东西变成可以异步的,比如 linux 原本没有提供不开线程的异步文件 io,那你也不可能通过协程变成可以不开额外线程的异步读写文件 因此从这个意义上说,协程就是语言提供的一种机制,简化异步代码的编写 关于和线程的对比,其实是异步 vs 同步的对比,然后协程可以让异步代码变得看起来像同步的一样,仅此而已 更一般地说,协程也未必真的要为了异步,也可以用作更一般化的逻辑解构,不过那就是另一个故事了 |
17
hhhsuan 2020-03-21 12:12:43 +08:00
如果不用切换线程就实现任务的调度那就是真协程,但 kotlin 给我的感觉是任务的调度还是通过线程切换完成的。
|
18
wanglufei 2020-03-21 12:25:04 +08:00 via Android
callback 的语法糖
|
20
qiyuey 2020-03-21 12:41:16 +08:00
可以从多个层面理解:
1、是否已经清楚 阻塞 和 非阻塞 的区别 2、是否已经清楚 Callback 的问题 3、是否已经清楚 Reactive 和 Coroutines 的区别 这三个问题是逐层递进的,需要一个一个理解 |
21
reus 2020-03-21 12:44:39 +08:00
@qiyuey 这是字眼上的区别???不同概念就是不同概念,自己没搞清楚就说别人抠字眼???不存在什么广义协程协程,只有正确理解和错误理解。
|
22
no1xsyzy 2020-03-21 13:26:31 +08:00
@abcbuzhiming 查了下,“用户态的轻量级多线程” 应该叫 “纤程” 而不是 “协程”,协程和线程是相互正交的两种概念。
|
23
yule111222 2020-03-21 13:43:10 +08:00
就是写起来简单点,确实是伪协程
|
24
no1xsyzy 2020-03-21 13:44:34 +08:00
协程的定义是用 yield 来主动交出控制权,具体的执行器到底有几个是不确定的。
引入协程式语法并不需要线程调度,转换 CSP 之后 yield 实质上把续延当作 callback 传递不就行了?写还是写类似同步的。 不过既然没有引入续延那就是这样封装一把容易(得多得多)。 |
28
Tyanboot 2020-03-22 02:30:06 +08:00
@hhhsuan #17 如果不用切换线程的话, 那 launch(Executors.newSingleThreadExecutor().asCoroutineDispatcher()) 是否符合呢. launch 的时候选择用单线程的调度器, 也就没有线程切换的问题了吧.
@no1xsyzy #24 另外如果定义可以用类似 yield 的方式来交出控制权的话, 那 kt 同样提供了 suspendCoroutine 和 suspendCancellableCoroutine 的方式来暂停, 并提供一个 Continuation 对象来供恢复. 这就和 Rust 的 Future 设计是差不多的意思. |
29
araaaa 2020-03-22 09:38:28 +08:00 via iPhone
将函数式代码转为命令式,减少嵌套提高可读性
|
30
no1xsyzy 2020-03-23 00:46:55 +08:00
@Tyanboot 实际上我刚花了半小时看了下 kotlin 语法(
感觉是 delay 之类的阻塞操作隐式交出控制权这样。隐式是可以的。 suspendCoroutine 没看太看明白…… 用法大概是 launch { // doSomething passed_value = suspendCoroutine( continuation -> some_global_variable = continuation) // after continued } launch { // doSomething some_global_variable.resume(pass_value) // not reachable } 这样?那其实就是做了个 first-class continuation (虽然这个 continuation 是个一次性的,到底算不算 first-class 我也不知道),并且把逃逸路线控制在 launch 上吧…… 怎么说…… 挺原汁原味的…… |
31
Tyanboot 2020-03-23 18:45:05 +08:00
@no1xsyzy 大概就是这样的用法, 不过 resume 的时候不需要放在 launch 里面, 毕竟 launch 只是用来启动一个协程的, kotlin 把这玩意叫可暂停的函数罢了。
delay 这个函数就长这样 suspend fun delay(timeMillis: Long) { if (timeMillis <= 0) return return suspendCancellableCoroutine sc@ { cont: CancellableContinuation<Unit> -> cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont) } } 里面也是直接调用了 suspendCancellableCoroutine 来暂停的. 这样很多阻塞操作用户看来感觉就像是隐式的, 其实都是函数调用链里面某一层显式的用了 suspend*Coroutine 来暂停的,包括什么 channel 的 receive,mutex 的 lock 之类的。 |