19 如何用协程来优化多线程业务?

465 阅读5分钟

大家好,我是小水珠。

近一两年,国内很多互联网公司开始使用或转型Go语言,一种一个很重要的原因就是Go语言优越的性能表现,而这个优势与Go语言实现的轻量级线程(协程)不无关系。那么Go协程的实现与Java线程的实现有什么区别呢?

一 线程实现模型

1. 1:1线程模型

我们知道在Linux操作系统编程中,往往都是通过fork()函数创建一个子进程来代表一个内核中的线程。一个进程调用fork()函数后,系统会先给新的进程分配资源,例如,存储数据和代码的空间。然后把原来进程所有值都复制到新的进程中,只有少数值与原来进程的值(比如PID)不同,这就相当于复制了一个主进程。

采用fork()创建子进程的方式来实现并行运行,会产生大量冗余数据,即占用大量内存空间,又消耗大量CPU时间时间用来初始化内存空间以及复制数据。

如果是一份一样的数据,为什么不共享主进程的这一份数据呢?这时候轻量级进程(Light Weight Process 即LWP)出现了。

相对于fork()系统调用创建的线程来说,LWP使用clone()系统调用创建线程,该函数是将部分父进程的资源的数据结构进行复制,复制内容可选,且没有被复制的资源可以通过指针共享给子进程。因此,轻量级进程的运行单元更小,运行速度更快。LWP是跟内核线程一对一映射的,每个LWP都是由一个内核线程支持。

2. N:1线程模型

该线程模型是在用户空间完成了线程的创建,同步,销毁和调度,已经不需要内核的帮助了,也就是说在线程的创建,同步,销毁的过程中不会产生用户态和内核态的空间切换,因此线程的操作非常快速且低消耗。

3. N:M线程模型

N:M线程模型是基于上诉两种线程模型实现的一种混合线程管理模型,即支持用户态线程通过LWP与内核线程连接,用户态的线程数量和内核态的LWP数量是N:M的映射关系。

二 协程的实现原理

相对于协程,你可能对进程和线程更为熟悉。进程一般对应一个应用服务,在一个应用服务中可以创建多个线程,而协程与进程的概念不一样,我们可以将协程看做是一个类函数或一块函数中的代码,我们可以在一个主线程里面轻松创建多个协程。

程序调用协程与调用函数不一样的是,协程可以通过暂停或阻塞的方式将协程的执行挂起,而其它协程可以继续执行。这里的挂起只是在程序中(用户态)的挂起,同时将代码执行权转让给其它协程使用,待获取执行权的协程执行完之后,将从挂起点唤醒挂起的协程。协程的挂起和唤醒是通过一个调度器来完成的。

结合下图,你可以更清楚的了解到基于N:M线程模型实现的协程是如何工作的。

假设程序中默认创建两个线程为协程使用,在主线程中创建协程ABCD.....分别存储在就绪队列中,调度器首先会分配一个工作线程A执行协程A,另外一个工作线程B执行协程B,其它创建的协程将会放在队列中进行排队等候。

19-协程分配.jpg

当协程A调用暂停方法或被阻塞时,协程A会进入到挂起队列,调度器会调用等待队列中的其它协程抢占线程A执行。当协程A被唤醒时,它需要重新进入到就绪队列中,通过调度器抢占线程,如果抢占成功,就继续执行协程A,失败则继续等待抢占线程。

19-协程挂起.jpg

三 Kilim协程框架

在Java中引入Kilim,和我们平时引入第三方组件不太一样,除了引入jar包之外,还需要通过Kilim提供的织入工具对Java编译生成的字节码进行增强处理。这样就可以在Java中使用协程了。

Kilim框架包含了四个核心组件,分别为:任务载体(Task),任务上下文(Fiber),任务调度器(Scheduler)以及通信载体(Mailbox)。

19-lkilim.jpg

四 协程与线程的性能比较

Java多线程实现源码:

微信图片_20220820171825.jpg 微信图片_202208201718251.jpg

Kilim协程框架实现源码:

微信图片_202208201718252.jpg 微信图片_202208201718253.jpg 微信图片_202208201718254.jpg

在这个案例中,我创建了1000个生产者和1000个消费者,每个生产者生产10个产品,1000个消费者同时消费产品。我们可以看到两个例子运行结果如下:

多线程执行时长:2761

协程执行时长:1050

五 总结

协程和线程密切相关,协程可以认为是运行在线程上的代码块,协程提供的挂起操作会使协程暂停操作,而不会导致线程阻塞。

协程又是一种轻量级资源,即使创建了上千个协程,对于系统来说也不是很大的负担,但如果在程序中创建上千个线程,那系统可就真亚历山大了。可以说,协程的设计方式极大的提高了线程的使用率。

通过今天的学习,当其他人侃侃而谈Go语言在网络编程中的优势时,相信你不会一头雾水。学习Java的我们也不要觉得,协程离我们很遥远。协程是一种设计思想,不仅仅局限于某一门语言,况且Java已经可以借助协程框架实现协程了。

但话说回来,协程还是Go语言中的应用较为成熟,在Java中的协程目前还不是很稳定,重点是缺乏大型项目的验证,可以说Java的协程设计还有很长的路要走。