详解golang中的Goroutines和Channels

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执行发送操作而向队列插入元素。

    推荐阅读