Goroutines 在go语言中,每一个并发执行的单元都叫做goroutine,类似其他语言中并发执行的最小单位——线程,它与线程的区别将在本文后面进行解释。
当一个程序启动时,其主函数即在一个单独的goroutine中运行,我们叫它main goroutine。新的goroutine会用go语句来创建。在语法上,go语句是一个普通的函数或方法调用前加上关键字go。go语句会使其语句中的函数在一个新创建的goroutine中运行。而go语句本身会迅速地完成。
主函数返回时,所有的goroutine都会被直接打断,程序退出。除了从主函数退出或者直接终止程序之外,没有其它的编程方法能够让一个goroutine来打断另一个的执行,但是之后可以看到一种方式来实现这个目的,通过goroutine之间的通信来让一个goroutine请求其它的goroutine,并让被请求的goroutine自行结束执行。
并发clock测试 这里实现一个顺序执行的时钟服务器,它会每隔一秒钟将当前时间写到客户端:
func testClock() {
listener,err := net.Listen("tcp","localhost:8080")
if err != nil {
log.Fatal(err)
}
for {
conn,err := listener.Accept()
if err != nil {
log.Fatal(err)
continue
}
handleConn(conn)
}
}func handleConn(c net.Conn) {
defer c.Close()
for{
_,err := io.WriteString(c,time.Now().Format("15:04:05\n"))
if err != nil {
return
}
time.Sleep(1 * time.Second)
}
}
【详解golang中的Goroutines和Channels】当前程序只允许一个客户端进行访问,其他客户端连接必须阻塞等待前的连接结束。
使用nc连接测试如下:
使用go语句进行并发处理:
for {
conn,err := listener.Accept()
if err != nil {
log.Fatal(err)
continue
}
go handleConn(conn)
}
效果如下:
客户端即可进行并发访问。
Channel goroutine作为Go语言中的并发单元,那么不同的goroutine之间需要传递信息的媒介,而channel,就是goroutine之间的通信机制,
一个channel是一个通信机制,它可以让一个goroutine通过它给另一个goroutine发送值信息。每个channel都有一个特殊的类型,也就是channels可发送数据的类型。一个可以发送int类型数据的channel一般写为chan int。channel的零值同样是 nil。
使用内置的make函数,我们可以创建一个channel:
ch := make(chan int) // ch has type 'chan int'
channel的操作主要有发送以及接收,在接收语句中,
<-
运算符写在channel对象之前。一个不使用接收结果的接收操作也是合法的。ch <- x// a send statement
x = <-ch // a receive expression in an assignment statement
<-ch// a receive statement;
result is discarded
Channel还支持close操作,用于关闭channel,随后对基于该channel的任何发送操作都将导致panic异常。对一个已经被close过的channel进行接收操作依然可以接受到之前已经成功发送的数据;如果channel中已经没有数据的话将产生一个零值的数据。
以最简单方式调用make函数创建的是一个无缓存的channel,但是我们也可以指定第二个整型参数,对应channel的容量。如果channel的容量大于零,那么该channel就是带缓存的channel。
ch = make(chan int)// unbuffered channel
ch = make(chan int, 0) // unbuffered channel
ch = make(chan int, 3) // buffered channel with capacity 3
不带缓存的Channels一个基于无缓存Channels的发送操作将导致发送者goroutine阻塞,直到另一个goroutine在相同的Channels上执行接收操作,当发送的值通过Channels成功传输之后,两个goroutine可以继续执行后面的语句。反之,如果接收操作先发生,那么接收者goroutine也将阻塞,直到有另一个goroutine在相同的Channels上执行发送操作。
因此,不带缓存的channel可以用来进行两个goroutine之间的同步。
串联的Channels(Pipeline)
Channels也可以用于将多个goroutine连接在一起,一个Channel的输出作为下一个Channel的输入。这种串联的Channels就是所谓的管道(pipeline)。下面的程序用两个channels将三个goroutine串联起来。
func main() {
naturals := make(chan int)
squares := make(chan int) go func() {
for i:=0;
;
i++ {
naturals <- i
}
}() go func() {
for {
n := <- naturals
squares <- n * n
}
}() for {
fmt.Println(<-squares)
}
}
在下面的改进中,我们的计数器goroutine只生成100个含数字的序列,然后关闭naturals对应的channel,这将导致计算平方数的squarer对应的goroutine可以正常终止循环并关闭squares对应的channel。(在一个更复杂的程序中,可以通过defer语句关闭对应的channel。)最后,主goroutine也可以正常终止循环并退出程序。
func main() {
naturals := make(chan int)
squares := make(chan int) go func() {
for i:=0;
i<100;
i++ {
naturals <- i
}
close(naturals)
}() go func() {
for n:= range naturals{
squares <- n * n
}
close(squares)
}() for {
for n:= range squares{
fmt.Println(n)
}
}
}
带缓存的Channels
带缓存的Channel内部持有一个元素队列。队列的最大容量是在调用make函数创建channel时通过第二个参数指定的。下面的语句创建了一个可以持有三个字符串元素的带缓存Channel。
ch = make(chan string, 3)
向缓存Channel的发送操作就是向内部缓存队列的尾部插入元素,接收操作则是从队列的头部删除元素。如果内部缓存队列是满的,那么发送操作将阻塞直到因另一个goroutine执行接收操作而释放了新的队列空间。相反,如果channel是空的,接收操作将阻塞直到有另一个goroutine执行发送操作而向队列插入元素。