GO语言:第五节

65 阅读5分钟
//接口

/*
接口即约定
前四节中的类型都是具体类型。
具体类型指定了它所含数据的精确布局,还暴露了基于这个布局的内部操作。
总之,如果你知道了一个具体类型的数据,那么你就知道它是什么以及能干什么。

接口是一种抽象类型,它并没有暴露所含数据的布局或者内部结构。
它仅仅提供一些方法。

如果一个具体类型要实现一个接口,那么必须实现接口类型定义的所有方法。

示例:
type person interface {
	getName() string
	getAge()  int
}
type tom struct {
	name string
	age  int
}
func (t *tom)getName() string {
	return t.name
}
func (t *tom)getAge() int {
	return t.age
}
t := tom{"Tom", 18}
fmt.Println( (&t).getName() )
fmt.Println( (&t).getAge() )

*/

Go有两种并发编程的风格:goroutine和通道(channel)

//goroutine

/*
在Go里,每一个并发执行的活动称为goroutine。
当一个程序启动时,只有一个goroutine来调用main函数,称它为主goroutine。
新的goroutine通过go语句进行创建。
语法上,一个go语句是在普通的函数或者方法调用前加上go关键字前缀。
go f()

示例:
package main
import (
	"fmt"
	"time"
)
func sayHello() {
	for {//死循环
		fmt.Println("Hello!")
		time.Sleep(time.Millisecond * 500)
	}
}
func fib(x int) int {
	if x < 2 {
		return x
	}
	return fib(x - 1) + fib(x - 2)
}
func main() {
	go sayHello()
	fmt.Println(fib(45))
}
示例中主goroutine顺序执行main函数,新建的goroutine顺序执行sayHello函数。
当main函数返回时,所有的gotoutine都暴力地直接终结,然后程序退出。
*/
//通道

/*
如果说goroutine是Go程序并发的执行体,通道就是它们之间的连接。
通道是可以让一个goroutine发送特定值到另一个goroutine的通信机制。
每一个通道是一个具体类型的通道。(如接收int类型消息的通道:chan int)
使用内置的make函数来创建一个通道:
ch := make(chan int)
像map一样,通道是一个使用make创建的数据结构的引用。
当复制或者作为参数传递到一个函数时,复制的是引用。

通道有主要两个操作:发送和接收。(对于goroutine而言)
发送就是一个goroutine传值到通道(或者一个通道接收值)
接收就是一个goroutine接收通道里的值(或者一个通道发送值)
ch := make(chan int)
ch <- x   //发送值给通道(通道接收值)
a := <-ch //赋值语句中的接收表达式
<-ch      //接收并丢弃结果

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

无缓冲通道上的发送操作将会阻塞,直到另一个goroutine在对应的通道上
执行接收操作,这时值传送完成,两个goroutine都可以继续执行。
所以如果接收操作先执行,接收方goroutine将会阻塞,直到发送方
goroutine在同一个通道上发送一个值。
使用无缓冲通道进行的通信导致发送和接收gotoutine同步化。
因此,无缓冲通道也称为同步通道。
package main

import (
	"fmt"
)

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

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

	go func() {
		for {
			x, ok := <-naturals
			if !ok {
				break
			}
			squares <- x * x
		}
		close(squares)
	}()

	for {
		x, ok := <-squares
		if !ok {
			break
		}
		fmt.Println(x)
	}
}
当接收方会从通道收到两个信息:第一个是值,第二个是布尔值
true代表接收成功
false代表接收操作在一个已经关闭且读取完数据的通道上。

Go语言中提供range循环语法在通道上的迭代。
当通道关闭且数据读完时退出循环。
所以上面的例子可以改成
package main

import (
	"fmt"
)

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

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

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

	for x := range squares {
		fmt.Println(x)
	}
}
*/
//单项通道

/*
当通道作为函数参数列表时,有三种不的写法:
输出通道(只能输出),输入通道(只能输入),输入输出通道(都可)

func(c chan int)
通道c可以输入输出(即发送方能给c发数据,接收方也能接收c中的数据)

func(in chan<- int)
输入通道in(即仅支持发送方给in发数据)

func(out <-chan int)
输出通道out(仅支持接收方接收out里的数据)

在任何赋值操作中双向通道可赋值给单项通道,反之不行。
*/
//缓冲通道

/*
缓冲通道有一个元素队列,队列的最大长度在创建的时候通过make容量参数设置。
ch = make(chan string, 3)

缓冲通道的特点:
如果通道已满,发送方会阻塞
如果通道为空,接收方会阻塞
如果通道既不空也不慢,就都不会阻塞

内置函数cap可以获取通道的容量
*/
//select

/*
select的形式和switch很像
但select必须和通道一起使用
select {
case <-ch1:
    //...
case x := <-ch2: //第二个参数可以不接收
    //...
case ch3 <- y:
    //...
default:
    //...
}
select会一直等待,直到某一个case能够通信,并执行(会执行最快完成通信的那个)。
如果有default而其他case阻塞的话,就会直接执行default。
*/