开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情
本节介绍 Go 语言中一些其它特性,比如泛型和并发。泛型是 Go 1.18版本中新加入的功能,而并发则是 Go 引以为傲的强大特性之一。
泛型
- Go函数通过使用类型参数可以支持多种类型。类型参数声明在函数参数之前的方括号内。
func Index[T comparable](s []T, x T) int {
for i, v := range s {
// v and x are type T, which has the comparable
// constraint, so we can use == here.
if v == x {
return i
}
}
return -1
}
func main() {
// Index works on a slice of ints
si := []int{10, 20, 15, -10}
fmt.Println(Index(si, 15))
// Index also works on a slice of strings
ss := []string{"foo", "bar", "baz"}
fmt.Println(Index(ss, "hello"))
}
- 除了通用函数,Go还支持通用类型。一个类型可以利用类型参数进行参数化,可以用于实现通用的数据结构。
type List[T any] struct {
next *List[T]
val T
}
并发
- goroutine是由Go运行时管理的轻量级线程。
go f(x, y, z)会启动一个新的goroutine并运行f(x, y, z)- 其中
f、x、y、z的求值发生在当前goroutine,而f的执行发生在新的goroutine中
- goroutine在相同的地址空间运行,所以访问共享内存时要同步
- **信道(channel)**是一种具有类型的管道,可以通过
<-操作符来发送和接收值。 - 信道需要创建后使用
ch := make(chan int) - 默认情况下,发送和接收操作在另一端准备好之前都会阻塞。这使得 Go 程可以在没有显式的锁或竞态变量的情况下进行同步。
- 信道可以带有缓冲,
make的第二个参数可以指定缓冲的长度。仅当信道的缓冲区填满后,向其发送数据时才会阻塞。当缓冲区为空时,接受方会阻塞。 - 发送方可以通过
close关闭一个信道来表示没有需要发送的值了。而接收方可以通过接收语句的第二个返回值测试信道是否被关闭。- 只有发送者可以关闭信道
- 信道不同于文件,一般情况下不需要关闭,只有在必须告知接收者没有其它发送值时才需要关闭。
- 循环语句
for i := range c可以从信道重复接收值,直到信道关闭。
func fibonacci(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c)
}
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
for i := range c {
fmt.Println(i)
}
}
select语句可以让一个goroutine同时等待多个通信操作。select会一直阻塞,直到某个case可以执行,如果多个case同时准备好了,它会随机选择一个执行。select中的默认case会在其它case都未准备好时执行。利用默认case可以实现非阻塞。
func main() {
tick := time.Tick(100 * time.Millisecond)
boom := time.After(500 * time.Millisecond)
for {
select {
case <-tick:
fmt.Println("tick.")
case <-boom:
fmt.Println("BOOM!")
return
default:
fmt.Println(" .")
time.Sleep(50 * time.Millisecond)
}
}
}
- Go的标准库提供
sync.Mutex类型实现互斥锁(mutex, mutual exclusion),包含Lock和Unlock两个方法。
// SafeCounter is safe to use concurrently.
type SafeCounter struct {
mu sync.Mutex
v map[string]int
}
// Inc increments the counter for the given key.
func (c *SafeCounter) Inc(key string) {
c.mu.Lock()
// Lock so only one goroutine at a time can access the map c.v.
c.v[key]++
c.mu.Unlock()
}
// Value returns the current value of the counter for the given key.
func (c *SafeCounter) Value(key string) int {
c.mu.Lock()
// Lock so only one goroutine at a time can access the map c.v.
defer c.mu.Unlock()
return c.v[key]
}
func main() {
c := SafeCounter{v: make(map[string]int)}
for i := 0; i < 1000; i++ {
go c.Inc("somekey")
}
time.Sleep(time.Second)
fmt.Println(c.Value("somekey"))
}