在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
并发编程表现为程序由若干个自主的活动单元组成。go有两种并发编程风格,一种是goroutine和通道,它们支持通信顺序进程(CSP),CSP是一个并发模式,在不同的执行体(goroutine)之间传递值,但是变量本身局限于单一的执行体。还有一种共享内存多线程的传统模型,它们和在其他主流语言中使用线程类似。 goroutine在go里面,每一个并发执行的活动称为goroutine,类似于线程。当一个程序启动时候,只有一个goroutine来调用main函数,称为主goroutine,新的goroutine通过go语句进行创建。 f() //调用f(),等待它返回 go f() //新建一个调用f()的grouting,不用等待 除了从main返回或者退出程序外,没有程序化的方法让一个goroutine来停止另一个,但是有办法和goroutine通信来要求它自己停止。 示例并发时钟服务器package main import( "io" "log" "net" "time" ) 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) } } func main() { listener,err:=net.Listen("tcp","localhost:8080") if err!=nil{ log.Fatal(err) } for{ conn,err:=listener.Accept() //阻塞,直到有连接请求进来 if err!=nil{ log.Print(err) //连接中止 continue } handleConn(conn) //一次处理一个连接 } } 当运行这段程序,使用nc命令连接本地8080端口时候,客户端会显示每秒从服务器发送的时间。使用killall clock1来终止指定名字的进程。 当第二个客户端需要链接进来的时候必须等第一个客户端结束,因为服务器是顺序的,一次只能处理一个客户请求。让服务器支持并发只需要一个很小的改变:在调用handleConn的地方添加一个go关键字,使它在自己的goroutine内执行。 for{ conn,err:=listener.Accept() //阻塞,直到有连接请求进来 if err!=nil{ log.Print(err) //连接中止 continue } go handleConn(conn) //并发处理连接 } 现在多个客户端可以同时接收到时间。 通道如果说goroutine是Go程序并发的执行体,通道就是它们之间的连接,每一个通道是一个具体类型的导管,叫做通道的元素类型。 ch := make(chan int) //ch类型是‘chan int’ ,创建一个无缓冲通道 ch := make(chan int,3) //容量为3的缓冲通道 像map一样,通道是一个使用make创建的数据结构的引用。和其他引用类型一样,零值为nil。通道由主要两个操作,发送和接收。 ch <- x //发送语句 x = <-ch //接收语句 <-ch //接收,丢弃结果 //第三个操作close,将设置一个标志位来指示这个通道后面没有值了; close(ch) 关闭后的发送操作将导致宕机,而接收操作将获取所有已发送的值。 使用make创建的通道叫做无缓冲通道,后面还有一个可选参数表示通道的容量,如果为0(默认值),则创建一个无缓冲通道。 无缓冲通道无缓冲通道,无论是发送操作还是接收操作都会阻塞,直到一方发送完毕或接收完毕。这样会导致两个goroutine同步化,因此无缓冲通道也叫同步通道。通过通道发送消息有一个值,有时候通信本身以及通信发生的时间也很重要,当我们强调这方面的时候,往往把消息称作事件。当事件没有携带额外消息,只是单纯的同步,我们通过使用一个struct{}元素类型通道强调它,尽管通常使用bool或int。 管道多个goroutine通过通道连接起来,一个输出是另一个的输入,就组成了管道。下面是三个goroutine,产生整数、求平方、输出。 package main import "fmt" func main() { naturals:=make(chan int) squares:=make(chan int) //counter go func() { for x:=0; ;x++{ naturals <- x } }() //squares go func() { for{ x:=<-naturals squares<-x*x } }() //printer for{ fmt.Println(<-squares) } } 如果发送方没有数据要发送,我们可以通过close关闭,所有发送操作会宕机,后续接收操作会获取零值。没有一个直接方式来判断通道是否关闭,但是有一个接收操作的变种,他产生两个结果:接收到的通道元素以及一个布尔值:true表示成功接收,false表示通道关闭。 //squares go func() { for{ x,ok:=<-naturals if !ok{ break //通道关闭 } squares<-x*x } close(squares)//通道读完关闭 }() 更为方便的是该语言提供了range循环语法以在通道上迭代,接受完最后一个值后关闭循环。 func main() { naturals:=make(chan int) squares:=make(chan int) //counter go func() { for x:=0; ;x++{ naturals <- x } close(naturals) }() //squares go func() { for x:=range naturals{ squares<-x*x } close(squares)//通道读完关闭 }() //printer for x:=range squares{ fmt.Println(x) } } 结束时关闭每一个通道不是必须的,因为go语言可以通过垃圾回收器根据它是否可以访问来决定是否回收它,而不是根据它是否关闭。(和文件close操作不一样,关闭文件是必须的)。 单向通道当一个通道用做函数的形参时,它几乎总是被有意地限制不能发送或接收。 chan<-int //只能发送的通道 <-chan int //只能接收的通道 因为close操作仅仅在发送方才能调用,所以试图关闭一个仅能接收的通道在编译时会报错。
func counter(out chan<-int) { for x:=0;x<100;x++{ out<-x } close(out) } //... func main() { naturals:=make(chan int) go counter(naturals) //... } 在任何赋值操作中,将双向通道转换为单向通道都是允许的,但反过来不行。 缓冲通道缓冲通道使得发送方可以无阻塞的发送缓冲容量大小的数据,接收方相反。所以当缓冲通道满,发送操作会阻塞,缓冲通道空,接收操作会阻塞。 ch := make(chan int,3) cap(ch) //获取通道容量 len(ch) //获取通道元素个数 向一个没有goroutine在接收的通道上不断发送会发生goroutine泄漏。 select多路复用select是Go中的一个控制结构,类似于用于通信的switch语句。每个case必须是一个通信操作,要么是发送要么是接收。select随机执行一个可运行的case。如果没有 case 可运行,它将阻塞,直到有 case 可运行。一个默认的子句应该总是可运行。 select { case communication clause : statement(s); case communication clause : statement(s); /* 你可以定义任意数量的 case */ default : /* 可选 */ statement(s); }
取消有时候我们需要让一个goroutine停止它当前的任务,一个goroutine无法直接终止另一个,因为这样会让所有的共享变量状态处于不确定状态。 当一个通道关闭且已取完所有发送的值后,接下来的接收操作会立即返回,得到零值。我们可以利用它创建一个广播机制:不在通道上发送值,而是关闭它。
|
请发表评论