Go Channel 详解

Channel是Go中的一个核心类型,你可以把它看成一个管道,通过它并发核心单元就可以发送或者接收数据进行通讯(communication)。

它的操作符是箭头 <-

1
2
ch <- v // 发送值v到Channel ch中
v := <-ch // 从Channel ch中接收数据,并将数据赋值给v

(箭头的指向就是数据的流向)

就像 map 和 slice 数据类型一样, channel必须先创建再使用:

1
ch := make(chan int)

阅读全文

Go泛型提案

Go泛型特性已经被讨论了多次了1, Andrew Gerrand现在把这个提案加入到了Go的issue列表中2,并标记为Go2标签。当然这不代表着要将泛型加入到Go中,而是演示一个完整的提案应该是什么样子。

阅读全文

使用Golang实现Futures 和 Promises

其它语言中Future和Promise的概念大量存在, 比如Node.js、Scala、Java、C#、C++ 11、Scheme、Swift等,可以方便的实现异步执行和回调。但是在Go语言的世界里,我们是通过goroutine/channel实现这种类似的功能呢,goroutine之间可以通过channel进行通讯, 但是,如果我们还是想使用Future/Promise的功能的话,该如何实现呢?

Future,Promise或Delay是用于并发编程的一种设计模式。它们表示一个对象,这个对象用来作为一次计算结果的代理,而该结果开始的时候是未知的,因为计算还没有完成。Promise与Future的区别在于,Future是Promise的一个只读的视图,也就是说Future没有设置任务结果的方法,只能获取任务执行结果或者为Future添加回调函数。

阅读全文

阿姆达尔定律

阿姆达尔定律(英语:Amdahl's law,Amdahl's argument),一个计算机科学界的经验法则,因吉恩·阿姆达尔(Gene Amdahl)而得名。它代表了处理器平行运算之后效率提升的能力。

1967年计算机体系结构专家吉恩.阿姆达尔提出过一个定律阿姆达尔定律,说:在并行计算中用多处理器的应用加速受限于程序所需的串行时间百分比。譬如说,你的程序50%是串行的,其他一半可以并行,那么,最大的加速比就是2。不管你用多少处理器并行,这个加速比不可能提高。在这种情况下,改进串行算法可能比多核处理器并行更有效。

阅读全文

Linux上下文切换监控

我们在监测Linux的应用的时候,当CPU的利用率非常高,但是系统的性能却上不去的时候,不妨监控一下线程/进程的切换,看看是不是context switching导致的overhead过高。

一般我使用dstat工具用来监控,比如dstat -y:

1
2
3
4
5
---system--
int csw
367 561
274 439
279 363

或者vmstat 3:

1
2
3
4
[root@abc smallnest]# vmstat 3
procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 0 0 3126192 31692 1521612 0 0 176 325 166 258 1 1 96 3 0

但是如何知道那些进程/线程做切换能,淘宝褚霸有篇文章:latencytop深度了解你的Linux系统的延迟介绍了一种方法。事实上,有一个工具pidstat,可以用来监控上下文切换。 它是sysstat包其中的一个工具,sysstat包含好几个很棒的工具,比如sar、iostat等。

执行pidstat -w

1
2
3
4
5
6
7
8
9
10
11
12
13
14
root@abc smallnest]# pidstat -w
Linux 2.6.32-358.el6.x86_64 (abc) 04/11/2016 _x86_64_ (2 CPU)
11:25:00 PM PID cswch/s nvcswch/s Command
11:25:00 PM 1 0.60 0.03 init
11:25:00 PM 2 0.04 0.00 kthreadd
11:25:00 PM 3 0.36 0.00 migration/0
11:25:00 PM 4 0.58 0.00 ksoftirqd/0
11:25:00 PM 5 0.01 0.00 migration/0
11:25:00 PM 6 0.08 0.00 watchdog/0
11:25:00 PM 7 0.39 0.00 migration/1
11:25:00 PM 8 0.01 0.00 migration/1
11:25:00 PM 9 0.52 0.00 ksoftirqd/1
……

cswch/s是主动地上下文切换,nvcswch/s是被动执行上下文切换的次数。

如要要显示线程的上下文切换统计,可以执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@abc smallnest]# pidstat -wt
Linux 2.6.32-358.el6.x86_64 (abc) 04/11/2016 _x86_64_ (2 CPU)
11:27:57 PM TGID TID cswch/s nvcswch/s Command
11:27:57 PM 1 - 0.56 0.03 init
11:27:57 PM - 1 0.56 0.03 |__init
11:27:57 PM 2 - 0.03 0.00 kthreadd
11:27:57 PM - 2 0.03 0.00 |__kthreadd
11:27:57 PM 3 - 0.34 0.00 migration/0
11:27:57 PM - 3 0.34 0.00 |__migration/0
11:27:57 PM 4 - 0.57 0.00 ksoftirqd/0
11:27:57 PM - 4 0.57 0.00 |__ksoftirqd/0
11:27:57 PM 5 - 0.01 0.00 migration/0
……

更多的参数可以man pidstat获得。

谁是最快的Go Web框架

前几天我写了一篇文章: 超全的Go Http路由框架性能比较,利用Julien Schmidt实现的benchmark测试框架对几乎所有的go web框架的路由功能进行了比较。我本来以为对Go web框架的性能考察就告以段落了,直到我写了一段简单的代码测试Irsi,用来模拟实际产品中的处理,才发现了Julien Schmidt测试框架的问题。

这段代码是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main
import (
"fmt"
"os"
"strconv"
"time"
"github.com/kataras/iris"
)
func main() {
api := iris.New()
api.Get("/rest/hello", func(c *iris.Context) {
sleepTime, _ := strconv.Atoi(os.Args[1])
if sleepTime > 0 {
time.Sleep(time.Duration(sleepTime) * time.Millisecond)
}
c.Text("Hello world")
})
api.Listen(":8080")
}

当我将实际业务的处理时间模拟为10毫秒的时候,使用100并发进行测试:
wrk -t16 -c100 -d30s http://127.0.0.1:8080/rest/hello,Iris吞吐率才达到97 requests/second。详细介绍可以看我的文章: iris 真的是最快的Golang 路由框架吗?

虽然Iris的作者很快做了修改,临时解决了这个问题,但是也促使我重新审视Julien Schmidt测试框架,也促使我实现了一个测试Go web framework benchmak的框架: Go web framework benchmark

2016/04/12 updated: 现在Iris已经改成了fasthttp实现,性能超级好。

阅读全文

iris 真的是最快的Golang 路由框架吗?

依照我的前一篇文章(超全的Go Http路由框架性能比较)对各种Go http路由框架的比较, Iris明显胜出,它的性能远远超过其它Golang http路由框架。

但是,在真实的环境中,Iris真的就是最快的Golang http路由框架吗?

2016-04-05 更新: 我已经提交了一个Bug, 作者Makis已经做了一个临时的解决方案,性能已经恢复,所以准备使用Iris的读者不必担心。
根据我的测试,最新的Iris的测试如下:

  1. 在业务逻辑需要10毫秒时,吞吐率可以达到9281 request/s
  2. 在业务逻辑需要1000毫秒时,吞吐率可以达到95 request/s
    性能已经很不错了。

我会做一个其它路由框架的测试,看看其它的框架是否也有本文所说的问题。

阅读全文

如何得到goroutine 的 id?

使用Java的时候很容易得到线程的名字, 比如"Thread.currentThread().getName",这样就可以进行一些监控操作或者设置线程相关的一些数据。当转向Golang开发的时候,却发现Go语言并没有提供获取当前goroutine id的操作。这是Golang的开发者故意为之,避免开发者滥用goroutine id实现goroutine local storage (类似java的"thread-local" storage), 因为goroutine local storage很难进行垃圾回收。因此尽管以前暴露出了相应的方法,现在已经把它隐藏了。

Please don't use goroutine local storage. It's highly discouraged. In fact, IIRC, we used to expose Goid, but it is hidden since we don't want people to do this.

Potential problems include:

  1. when goroutine goes away, its goroutine local storage won't be GCed. (you can get goid for the current goroutine, but you can't get a list of all running goroutines)
  2. what if handler spawns goroutine itself? the new goroutine suddenly loses access to your goroutine local storage. You can guarantee that your own code won't spawn other goroutines,
    but in general you can't make sure the standard library or any 3rd party code won't do that.

thread local storage is invented to help reuse bad/legacy code that assumes global state, Go doesn't have legacy code like that, and you really should design your code so that state is passed explicitly and not as global (e.g. resort to goroutine local storage)

当然Go的这种隐藏的做法还是有争议的,有点因噎废食。在debug log的时候goroutine id是很好的一个监控信息。本文介绍了两种获取goroutine id的方法。

阅读全文