[开发语言 | Go] 03 - 泛型、并发

76 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情

本节介绍 Go 语言中一些其它特性,比如泛型和并发。泛型是 Go 1.18版本中新加入的功能,而并发则是 Go 引以为傲的强大特性之一。

泛型

  1. 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"))
}
  1. 除了通用函数,Go还支持通用类型。一个类型可以利用类型参数进行参数化,可以用于实现通用的数据结构。
type List[T any] struct {
	next *List[T]
	val  T
}

并发

  1. goroutine是由Go运行时管理的轻量级线程。
    1. go f(x, y, z)会启动一个新的goroutine并运行f(x, y, z)
    2. 其中fxyz的求值发生在当前goroutine,而f的执行发生在新的goroutine中
  2. goroutine在相同的地址空间运行,所以访问共享内存时要同步
  3. **信道(channel)**是一种具有类型的管道,可以通过<-操作符来发送和接收值。
  4. 信道需要创建后使用ch := make(chan int)
  5. 默认情况下,发送和接收操作在另一端准备好之前都会阻塞。这使得 Go 程可以在没有显式的锁或竞态变量的情况下进行同步。
  6. 信道可以带有缓冲,make的第二个参数可以指定缓冲的长度。仅当信道的缓冲区填满后,向其发送数据时才会阻塞。当缓冲区为空时,接受方会阻塞。
  7. 发送方可以通过close关闭一个信道来表示没有需要发送的值了。而接收方可以通过接收语句的第二个返回值测试信道是否被关闭。
    1. 只有发送者可以关闭信道
    2. 信道不同于文件,一般情况下不需要关闭,只有在必须告知接收者没有其它发送值时才需要关闭。
  8. 循环语句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)
	}
}
  1. select语句可以让一个goroutine同时等待多个通信操作。select会一直阻塞,直到某个case可以执行,如果多个case同时准备好了,它会随机选择一个执行。
  2. 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)
		}
	}
}
  1. Go的标准库提供sync.Mutex类型实现互斥锁mutex, mutual exclusion),包含LockUnlock两个方法。
// 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"))
}