go语言中的main函数也是运行在一个单独的goroutine中的,一般称为 main goroutine,main函数结束时,会打断其它 goroutine 的执行,但是其它 goroutine 不会打断其它的 goroutine 的执行,除非是通过通信让对方自行中止。

  先来看一个最简单的并发例子,一个时间服务器:(偏题了,不应该使用这个例子,应该突出重点,这个例子可以放到tcp那节)

func main() {
listener, err := net.Listen("tcp", "localhost:8000")
if err != nil {
log.Fatal(err)
}
for {
conn, err := listener.Accept()
if err != nil {
log.Print(err)
continue
}
go handleConn(conn)
}
} func handleConn(c net.Conn) {
defer c.Close()
for {
_, err := io.WriteString(c, time.Now().Format("2006/01/02 15:04:05\n"))
if err != nil {
return
}
time.Sleep( * time.Second)
}
}

  从这个例子可以看出,使用go开启一个tcp服务非常简洁,此外从该例子中,我们可以顺便了解一下go中格式化日期和时间的方式是非常奇葩的,格式化模板限定为Mon Jan 2 03:04:05PM 2006 UTC-0700,可以这样记:1月2日下午3点4分5秒(200)6年UTC-0700。

  可以用 nc 或 telnet 来连接这个tcp服务进行测试, 也可以使用go实现一个简单的客户端:

func main() {
conn, err := net.Dial("tcp", "localhost:8000")
if err != nil {
log.Fatal(err)
}
defer conn.Close()
mustCopy(os.Stdout, conn)
} func mustCopy(dst io.Writer, src io.Reader) {
if _, err := io.Copy(dst, src); err != nil {
log.Fatal(err)
}
}

goroutine是通过 channel 来通信的, 包括发送和接收两种操作。可以通过 make 来声明一个channel 如 ch = make(chan int),和 map 类似,channel 也只是对应底层数据结构的引用,所以发送方和接收方都将引用同一份对象,channel是引用类型,所以它的零值是 nil。channel 使用的操作符是 <- ,发送操作是指将数据发送到 channel,接收是指从 channel中接收。channel类似于一般的io系统,可以通过 close(ch) 来关闭一个 channel,关闭后,将不能进行发送操作,但可以接收之前未接收完的数据,如果没有数据可接收,则接收到一个nil。

通过 make(chan int) 这种方式创建的 channel 称之为无缓存channel,表示 channel的容量是0,如果要声明一个有缓存的channel,可以使用 make(chan int, 100) ,第二个参数表示初始化时的channel容量大小。

一个基于无缓存Channels的发送操作将导致发送者goroutine阻塞,直到另一个goroutine在相同的Channels上执行接收操作,当发送的值通过Channels成功传输之后,两个goroutine可以继续执行后面的语句。反之,如果接收操作先发生,那么接收者goroutine也将阻塞,直到有另一个goroutine在相同的Channels上执行发送操作。

简单的说,就是使用无缓存channel时,一旦通信发起,必须要等到通信完成才可以继续执行,否则将阻塞等待。这也就意味着,无缓存channel在通信时,会强制进行一次同步操作,所以无缓存channel也称之为同步channel

下面是一个串联channel的例子:

func main() {
ch1 := make(chan int)
ch2 := make(chan int) go func() {
for x := ; x <= ; x++ {
ch1 <- x
}
close(ch1)
}() go func() {
for x := range ch1 {
ch2 <- x * x
}
close(ch2)
}() for x := range ch2 {
fmt.Println(x)
}
}

不管一个channel是否被关闭,当它没有被引用时将会被Go语言的垃圾自动回收器回收。(不要将关闭一个打开文件的操作和关闭一个channel操作混淆。对于每个打开的文件,都需要在不使用的使用调用对应的Close方法来关闭文件。)试图重复关闭一个channel或者关闭一个nil值的channel都将导致panic异常,此外关闭一个channels还会触发一个广播机制。

因为关闭操作只用于断言不再向channel发送新的数据,所以只有在发送者所在的goroutine才会调用close函数,因此对一个只接收的channel调用close将是一个编译错误。

单方向channel,只发送或只接收,功能明确。类型chan<- int表示一个只发送int的channel,只能发送不能接收。相反,类型<-chan int表示一个只接收int的channel,只能接收不能发送。(箭头<-和关键字chan的相对位置表明了channel的方向。)这种限制将在编译期检测。

func counter(out chan<- int) {
for x := ; x < ; x++ {
out <- x
}
close(out)
} func squarer(out chan<- int, in <-chan int) {
for v := range in {
out <- v * v
}
close(out)
} func printer(in <-chan int) {
for v := range in {
fmt.Println(v)
}
} func main() {
naturals := make(chan int)
squares := make(chan int)
go counter(naturals)
go squarer(squares, naturals)
printer(squares)
}

带缓存的 channel,内部持有一个元素队列。队列的最大容量是在调用make函数创建channel时通过第二个参数指定的。向缓存Channel的发送操作就是向内部缓存队列的尾部插入原因,接收操作则是从队列的头部删除元素。如果内部缓存队列是满的,那么发送操作将阻塞直到因另一个goroutine执行接收操作而释放了新的队列空间。相反,如果channel是空的,接收操作将阻塞直到有另一个goroutine执行发送操作而向队列插入元素。

内置的 cap 函数可以查看某个 channel 的容量,len 函数可以查看某个 channel 的当前缓存队列中有效元素的个数。

如果我们使用了无缓存的channel,那么两个慢的goroutines将会因为没有人接收而被永远卡住。这种情况,称为goroutines泄漏,这将是一个BUG。和垃圾变量不同,泄漏的goroutines并不会被自动回收,因此确保每个不再需要的goroutine能正常退出是重要的。

关于无缓存或带缓存channels之间的选择,或者是带缓存channels的容量大小的选择,都可能影响程序的正确性。无缓存channel更强地保证了每个发送操作与相应的同步接收操作;但是对于带缓存channel,这些操作是解耦的。同样,即使我们知道将要发送到一个channel的信息的数量上限,创建一个对应容量大小带缓存channel也是不现实的,因为这要求在执行任何接收操作之前缓存所有已经发送的值。如果未能分配足够的缓冲将导致程序死锁。

如果生产线的前期阶段一直快于后续阶段,那么它们之间的缓存在大部分时间都将是满的。相反,如果后续阶段比前期阶段更快,那么它们之间的缓存在大部分时间都将是空的。对于这类场景,额外的缓存并没有带来任何好处。

我的理解是,当通信双方的工作效率接近时,缓存才有意义,如果一方处理过快,而另一方过慢,则效率由短板决定,缓存并不能起到提升效率的作用。

golang笔记——并发的更多相关文章

  1. golang的并发

    Golang的并发涉及二个概念: goroutine channel goroutine由关键字go创建. channel由关键字chan定义 channel的理解稍难点, 最简单地, 你把它当成Un ...

  2. golang的并发不等于并行

    先 看下面一道面试题: func main() { runtime.GOMAXPROCS(1) wg := sync.WaitGroup{} wg.Add(20) for i := 0; i < ...

  3. golang笔记1

    golang笔记1 go代码是用包来组织的,每个包有一个或多个go文件组成,这些go文件文件放在一个文件夹中 每个源文件开始都用一个package声明,指明本源文件属于哪个包 pakage声明后紧跟这 ...

  4. Golang笔记(二)面向对象的设计

    Golang笔记(二)面向对象的设计 Golang本质还是面向过程的语言,但它实现了一些OOP的特性,包括抽象.封装.继承和多态. 抽象和封装 Golang和C语言一样以struct为数据结构核心,不 ...

  5. Golang笔记(一)简洁的语言风格

    Golang笔记(一)简洁的语言风格 概述 Golang继承了很多C语言的风格,寡人使用了十几年C语言,切换到Golang时上手很快,并且随着深入的使用,越来越喜欢这门语言.Golang最直观的感受是 ...

  6. go---weichart个人对Golang中并发理解

    个人觉得goroutine是Go并行设计的核心,goroutine是协程,但比线程占用更少.golang对并发的处理采用了协程的技术.golang的goroutine就是协程的实现. 十几个gorou ...

  7. golang实现并发爬虫三(用队列调度器实现)

    欲看此文,必先可先看: golang实现并发爬虫一(单任务版本爬虫功能) gollang实现并发爬虫二(简单调度器) 上文中的用简单的调度器实现了并发爬虫. 并且,也提到了这种并发爬虫的实现可以提高爬 ...

  8. golang学习笔记----并发

    并发模型 并发目前来看比较主流的就三种: 多线程:每个线程一次处理一个请求,线程越多可并发处理的请求数就越多,但是在高并发下,多线程开销会比较大. 协程:无需抢占式的调度,开销小,可以有效的提高线程的 ...

  9. golang笔记——函数与方法

    如果你遇到没有函数体的函数声明,表示该函数不是以Go实现的. package math func Sin(x float64) float //implemented in assembly lang ...

随机推荐

  1. OpenStack云计算快速入门之三:OpenStack镜像管理

    原文:http://blog.chinaunix.net/uid-22414998-id-3272059.html 第三部分 OpenStack镜像管理 一.简介 很多源都有为OpenStack已经编 ...

  2. Eclipse不自动编译java文件的终极解决方案

    最近我的eclipse经常犯傻,项目中总是有很多,启动项目也是没有启动类.查了下项目中生成的class文件,我靠竟然没有,或者还是以前的.原来是eclipse犯傻了,它没帮我自动编译java文件.一般 ...

  3. 权重轮询调度算法(WeightedRound-RobinScheduling)-Java实现2

    权重轮询调度算法(WeightedRound-RobinScheduling)-Java实现 ----参考Nginx中负载均衡算法实现 与上一遍博客 http://www.cnblogs.com/hu ...

  4. Linux下定时执行脚本(转自Decode360)

    文章来自:http://www.blogjava.net/decode360/archive/2009/09/18/287743.html Decode360's Blog  老师(业精于勤而荒于嬉 ...

  5. 烂泥:openvpn配置文件详解

    本文由秀依林枫提供友情赞助,首发于烂泥行天下 在上一篇文章<烂泥:ubuntu 14.04搭建OpenVPN服务器>中,我们主要讲解了openvpn的搭建与使用,这篇文章我们来详细介绍下有 ...

  6. Eclipse 安装 jBPM 插件

    下载jbpm-installer安装包并解压,找到 jbpm-installer\build.properties搜索eclipse.home 修改 eclipse.home=./eclipse 成 ...

  7. 学习大神笔记之 “MyBatis学习总结(一)”

    1.准备工作 软件:eclipse. mysql .navicat for mysql 包:mybatis-3.1.1.jar   mysql-connector-java-5.1.7-bin.jar ...

  8. Oracle闪回技术详解

     概述: 闪回技术是Oracle强大数据库备份恢复机制的一部分,在数据库发生逻辑错误的时候,闪回技术能提供快速且最小损失的恢复(多数闪回功能都能在数据库联机状态下完成).需要注意的是,闪回技术旨在快速 ...

  9. java中Class对象详解和类名.class, class.forName(), getClass()区别

    一直在想.class和.getClass()的区别,思索良久,有点思绪,然后有网上搜了搜,找到了如下的一篇文章,与大家分享. 原来为就是涉及到Java的反射----- Java反射学习 所谓反射,可以 ...

  10. Winform菜单和工具栏控件

    1.ContextMenuStrip--右键菜单 可以绑定在任何一个控件上,添加操作快捷键,并可以设置多层 每行相当于一个按钮,输入-可添加分割线 2.MenuStrip--菜单 优先级最高,一定会出 ...