Go语言中并发程序能够用两种方式来实现。一种是goroutine和channel,其支持“顺序进程通讯”(communicating sequential processes)或被简称为CSP。CSP是一个现代的并发编程模型,在这种编程模型中值会在不一样的运行实例(goroutine)中传递,尽管大多数状况下被限制在单一实例中。另外一种是传统的并发模型,多线程共享内存(基于共享变量的并发),会在后续单独阐述。编程
goroutine是一个轻量级的执行线程(又称协程),它与线程的区别是线程是操做系统中对于一个独立运行实例的描述,不一样的操做系统中,线程的实现也不尽相同;对于goroutine,操做系统并不知道它的存在,goroutine的调度是Go语言的运行时进行管理的。启动线程虽然比进程使用的资源少,但依然须要上下文切换等大量工做,Go语言有本身的调度器,许多goroutine的数据都是共享的,所以goroutine之间的切换会快不少,启动goroutine所耗费的资源也不多。缓存
package main
import (
"fmt"
"strconv"
)
func Info(name string) {
for i := 0; i < 3; i++ {
fmt.Println(name + ":" + strconv.Itoa(i))
}
}
func main() {
Info("info")
go Info("goroutine1")
go func(name string) {
fmt.Println(name)
}("goroutine2")
var input string
fmt.Scanln(&input)
fmt.Println("done")
}
复制代码
channel被称为通道,是链接并发goroutine的管道,能够从一个goroutine向通道发送值,并在另外一个goroutine中接收到这些值。每一个channel都有一个特殊的类型,也就是channel可发送数据的类型。一个能够发送int类型数据的channel通常写为chan int。多线程
一个channel有发送和接收两个主要操做,都是通讯行为。一个发送语句将一个值从一个goroutine经过channel发送到另外一个执行接收操做的goroutine。发送和接收两个操做都是用<-
运算符。在发送语句中,<-
运算符分割channel和要发送的值;在接收语句中,<-
运算符写在channel对象以前,一个不使用接收结果的接收操做也是合法的。并发
channel的发送操做将致使发送者goroutine阻塞,直到另外一个goroutine在相同的channel上执行接收操做,当发送的值经过channel成功传输以后,两个goroutine能够继续执行后面的语句。反之,若是接收操做先发生,那么接收者goroutine也将阻塞,直到有另外一个goroutine在相同的Channels上执行发送操做。函数
package main
import (
"fmt"
"strconv"
)
func Info(i int, ch chan string) {
msg := "数据" + strconv.Itoa(i)
ch <- msg
}
func main() {
chs := make([]chan string, 3)
for i := 0; i < 3; i++ {
chs[i] = make(chan string)
go Info(i, chs[i])
}
for num, ch := range chs {
msg := <- ch
fmt.Println(num, msg)
}
fmt.Println("Done")
}
复制代码
channel还支持close操做,用于关闭channel,随后对基于该channel的任何发送操做都将致使Panic异常。对一个已经被close过的channel接收操做依然能够接受到以前已经成功发送的数据;若是channel中已经没有数据的话将产生一个零值(nil)的数据。ui
//使用内置的close函数就能够关闭一个channel:
close(ch)
复制代码
带缓存的channel内部有一个元素队列。队列的最大容量是在调用make函数建立channel时经过第二个参数指定的。下面的语句建立了一个能够持有三个字符串元素的带缓存channel。spa
ch = make(chan string, 3)
复制代码
向缓存channel的发送操做就是向内部缓存队列的尾部插入元素,接收操做则是从队列的头部删除元素。若是内部缓存队列是满的,那么发送操做将阻塞直到另外一个goroutine执行接收操做而释放了新的队列空间。相反,若是channel是空的,接收操做将阻塞直到有另外一个goroutine执行发送操做而向队列插入元素。操作系统
package main
import (
"fmt"
)
func main() {
ch := make(chan string, 3)
ch <- "A"
ch <- "B"
ch <- "C"
fmt.Println(<-ch)
fmt.Println(<-ch)
fmt.Println(<-ch)
}
复制代码
Go语言的select的功能和select、poll、epoll类似,就是监听IO操做,当IO操做发生时,触发响应的动做。select语句的用法和switch类似,也会有几个case和default分支,每个case表明一个通讯操做(在某个channel上进行发送或者接收)而且会包含一些语句组成一个语句块。线程
package main
import (
"fmt"
"strconv"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
ch1 <- "I am from ch1"
}()
go func() {
ch2 <- "I am from ch2"
}()
for i := 0; i < 2; i++ {
select {
case msg1 := <- ch1:
fmt.Println("received-"+strconv.Itoa(i), msg1)
case msg2 := <- ch2:
fmt.Println("received-"+strconv.Itoa(i), msg2)
}
}
}
复制代码
select是用来让咱们的程序监听多个文件句柄的状态变化的处理机制。当发起一些阻塞的请求后,能够用select机制轮询扫描文件句柄,直到被监视的文件句柄有一个或多个发生了状态改变。channel在系统层面来讲也是个文件描述符,在Go语言中咱们能够用goroutine并发执行任务,接着使用select来监视每一个任务的channel状况。若是这几个任务都长时间没有回复channel信息,而且咱们又有超时的需求,那么咱们可使用一个goroutine来设置超时机制,具体作法就是启动sleep而且在sleep以后回复channel信号。code
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan string)
timeout := make(chan bool)
go func() {
time.Sleep(5 * time.Second)
timeout <- true
}()
go func() {
time.Sleep(10 * time.Second)
ch <- "Hello World"
}()
select {
case msg := <- ch:
fmt.Println(msg)
case <- timeout:
fmt.Println("task is timeout")
}
}
复制代码