Go程序设计语言读书笔记-第八章

第八章-goroutine和通道

goroutine:在go里,每一个并发执行的活动成为goroutine。语法上,一个go语句实在普通的函数或者方法调用前加上go关键字。

go f() //新建一个f()的goroutine,不用等待

 

通道:如果说goroutine是Go程序并发的执行体,通道就是他们之间的连接。通道是可以让一个叫goroutine发送特定值到另一个goroutine的通信机制。每一个通道是一个具体类型的导管,叫做通道的元素类型。一个有int类型元素的通道写为chan int。

 

使用make函数来创建一个通道:

ch := make(chan int)   // ch的类型是 ‘chan int’

像map一样,通道是一个使用make创建的数据结构的引用。当复制或者作为参数传递到一个函数事,复制的是引用,这样调用者和被调用者都引用同一份数据结构。和其他引用类型一样,通道的零值是nil。

同种类型的通道可以使用==进行比较。当二者都是同一通道数据的引用时,比较值为true。通道也可以和nil进行比较。

通道主要有两个操作:发送和接收,两者称为通信

ch <- x  //发送语句
x = <-ch  //赋值语句中的接收表达式
<-ch    //接收语句,丢弃结果

 

 

通道支持第三个操作:关闭,他设置一个标志位来指示值当前已经发送完毕,这个通道后面没有值了;关闭后的发送操作将导致宕机。
在一个已经关闭的通道上进行接收,将获取所有已经发送的值,直到通道为空;这时任何接收操作会立刻完成,同时获取到一个通道元素类型对应的零值。

close(ch)

 

无缓冲通道:

无缓冲通道上的发送操作将会阻塞,知道另一个goroutine在对应的通道上执行接收操作,这是值传送完成,两goroutine都可以继续执行。相反,如果接受操作先执行,接收方goroutine将阻塞,直到另一个goroutine在同一个通道上发送一个值。

使用无缓冲通道进行的通信导致发送和接收goroutine同步化。当一个值在无缓冲通道上传递时,接收值后发送方goroutine才能再次被唤醒。

func main() {
	conn, err := net.Dial("tcp", "localhost:8000")
	if err != nil {
		log.Fatal(err)
	}
	done := make(chan struct{})
	go func() {
		io.Copy(os.Stdout, conn) // NOTE: ignoring errors
		log.Println("done")
		done <- struct{}{} // signal the main goroutine
	}()
	mustCopy(conn, os.Stdin)
	conn.Close()
	<-done // wait for background goroutine to finish
}

 

每一个消息有一个值,但有时候通信本身以及通信发生的时间也很重要。当我们强调这方面的时候,把消息叫做事件。当事件没有携带额外的信息时,它单纯的目的使进行同步。

管道:通道可以用来连接goroutine,这样一个得输出是另一个得输入。

func main() {
	naturals := make(chan int)
	squares := make(chan int)

	// Counter
	go func() {
		for x := 0; x < 100; x++ {
			naturals <- x
		}
		close(naturals)
	}()

	// Squarer
	go func() {
		for x := range naturals {
			squares <- x * x
		}
		close(squares)
	}()

	// Printer (in main goroutine)
	for x := range squares {
		fmt.Println(x)
	}
}

 

 

并发的Web爬虫:程序得并行度太高了,无限制得并行通常不是一个好得注意,因为系统中总有限制因素,例如CUP得核数,对于磁盘I/O操作磁头和磁盘得个数,下载流所使用得网络带宽等。解决办法是根据资源可用情况限制并发得个数,以匹配合适得并行度。

var tokens = make(chan struct{}, 20)

func crawl(url string) []string {
    fmt.Println(url)
    tokens <- struct{}{} // acquire a token
    list, err := links.Extract(url)
    <-tokens // release the token

    if err != nil {
        log.Print(err)
    }
    return list
}

 

在执行之前调用tokens <- struct{}{}获取一个名额,执行结束后调用<-tokens释放名额。

select多路复用:像switch语句一样,有一系列的情况和一个可选的默认分支。每一个情况指定一次通信(在一些通道上进行发送或接收操作)和关联的一段代码。
接收操作可以出现在它本身上,也可以用引用所接受的值。

select一直等待,直到一次通信来告知有一些情况可以执行,它执行这次通信,其他的通信将不会发生。没有对应情况的select,将永远等待。

 

func main() {
	ch :=make(chan int,20)   
	for i:=0;i<10;i++{
		select {
			case x := <- ch:
				fmt.Println(x)
			case ch<-i:
		}
	}

}

 

上面,如果多个情况同时满足,select随机选择一个,因为当缓冲区既不空也不满的情况,select相当于在掷硬币选择。

 

select {
	case <- abort:
		fmt.Printf("Launch aborted!\n")
		return
	default:
		//不执行任何操作
}

非阻塞通信。通道的零值是nil。在nil通道上发送和接收将永远阻塞,对于select语句中的情况,如果其通道是nil,他将永远不会被选择。称为通道的轮询。对于select,如果其通道是nil,他将永远不会被选择。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值