1.我对协程的认识
协程底层也是一个线程池来维护执行,只是将原来的多线程执行方法书写方式,更加同步化;
将异步任务存储在用户态应用数据结构中,然后由语言来控制执行,并保存其各个的执行栈;
用少数的线程执行大量的异步任务,类似golang底层封装了io及网络等操作,碰到block及busy的操作,直接将其挂起执行其他协程任务;
与线程池的不同是,线程池只是复用线程,将任务存储在数据结构中,但是碰到io等操作只能将线程挂起,切换到其他线程,无法做到少数线程处理大量io等操作任务;
2.Kotlin的协程如果对比Golang来说,其实是 "假协程",其实可以看作是一种语法糖将异步回调的写法同步化,底层其实依然是使用的jvm的锁关键字等维护的线程池任务;
举个例子:
执行10w个任务,睡眠3秒后打印数字,使用Golang来实现:
package main
import (
"strconv"
"time"
)
func say0(str string) {
time.Sleep(3 * time.Second)
println(str)
}
func main() {
for i := 0; i < 100000; i++ {
go say0("协程" + strconv.Itoa(i))
}
time.Sleep(1000 * time.Second)
}
结果是,3秒后10w个任务全部打印完成,是我们要的效果;
如果使用Kotlin呢:
object K1 {
@Throws(InterruptedException::class)
fun say(s: String?) {
Thread.sleep(3000)
println(s)
}
@Throws(InterruptedException::class)
@JvmStatic
fun main(args: Array) {
for (i in 0..99999) {
GlobalScope.launch {
say("" + i)
}
}
LockSupport.park()
}
}
三秒后,打印12个任务,并且重复这样的打印效果;12是因为我电脑的核心数为12;
翻译成Java的写法其实是:
public class T2 {
public static void say(String s) throws InterruptedException {
Thread.sleep(3000);
System.out.println(s);
}
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(12);
for (int i = 0; i < 100000; i++) {
int finalI = i;
executorService.execute(() -> {
try {
say(finalI + "");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
LockSupport.park();
}
}
其实就是使用了一个固定线程数为12个的线程池来执行任务;
这个例子我不知道阐述这种观点对不对,不过由此可以,如果遇到大量io的操作行为,Kotlin的这种方式并不能带来什么性能提升抑或是缩减内存的占用;
我觉得Golang的协程优势,充分体现在这种高io阻塞的场景上,以少数线程来执行大量任务;如果是cpu计算场景,则跟线程池比没有任何优势;
如果假想,以上例子是瞬间来了10w的请求,并且每个请求时间都有阻塞的话,则Jvm语言的处理方式可能需要开启10w个线程来执行,从而引发oom及切换线程的高额代价;Golang则可以从容面对,以每个协程4kb的代价,使用相当于核心线程数的线程数量即可完成任务处理;