##Goroutine与Libtask协程切换性能比较
##Libtask
Libtask是一个非常轻量级的C语言协程库,作者是大名鼎鼎的拉斯.考克斯,也是Go语言的作者之一。Libtask的实现原理也很简单,主要是利用系统调用如:getcontext、setcontext(注:不同的操作系统有不同的系统调用)封装成makecontext、swapcontext两个函数,利用这两个函数实现在单个线程中执行多个task,协程上下文切换非常轻量,因为是在同一个线程中,所以只需要保存和设置SP和PC就可实现上下文切换。
###Goroutine
Goroutine的实现应该有参考Libtask,主体思想都差不多,只不过Goroutine扩展到可以支持多核处理器,系统调用、IO等阻塞都会移到一个独立的线程中,释放阻塞线程,以便其他协程能够继续处理;Libtask也支持异步IO和同步IO,同步IO会阻塞线程,异步IO不会阻塞线程,并且多个异步非阻塞IO会放到同一个异步协程处理,很遗憾Libtask目前仅支持poll,暂不支持epoll,不过可以自己实现。
###对比测试
为了对比协程切换性能,设计一个简单的测试用例,分别切换100000000次,比较运行时间。
###测试环境
硬件:MacBook Pro, 2.5 GHz Intel Core i7,16 GB 1600 MHz DDR3
OS: OS X EI Captain
Go编译器版本:1.6.2
C编译器版本:clang-703.0.29 + LLVM
####GO测试代码:
```
package main
import(
"fmt"
"time"
"runtime"
)
const TOTAL_COUNTS = 100000000
func test(c chan int) {
count := 0
for {
count++
if count > TOTAL_COUNTS {
c <- 1
break
}
runtime.Gosched()
}
}
func main() {
runtime.GOMAXPROCS(1)
c := make(chan int, 0)
startTime := time.Now()
go test(c)
<-c
fmt.Println("USEAGE TIME:", time.Since(startTime))
}
```
####Libtask测试代码
```
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <task.h>
const int STACK_32K = 32678;
const int TOTAL_COUNTS = 100000000;
struct timeval tv_begin, tv_end;
void test()
{
int count = 0;
while(1) {
count++;
if (count > TOTAL_COUNTS) {
gettimeofday(&tv_end, NULL);
int time_usage = (tv_end.tv_sec - tv_begin.tv_sec) * 1000 + (tv_end.tv_usec- tv_begin.tv_usec)/1000;
printf("USAGE TIME: %d\n", time_usage);
break;
}
taskyield();
}
}void taskmain(int argc, char** args)
{
gettimeofday(&tv_begin, NULL);
taskcreate(test, NULL, STACK_32K);
}
```
###测试结果
libtask: 10412ms
Goroutine: 10965ms
###结果分析
从结果来看基本相当,Goroutine只是略微慢些,其原因是Go语言有Runtime和GC,执行效率本身就略慢于C语言;另外Goroutine支持多核,有多个处理器,有线程池,那么诸如任务队列、Channel必然会存在竞争,有竞争必然用到锁,这会进一步影响其性能。
综上,Goroutine的协程切换效率非常棒,与单线程的Libtask相差无几,但是Goroutine可以利用多核处理器来提高并发,微量的性能影响也就可以忽略不计了。
有疑问加站长微信联系(非本文作者)