并发编程时遇到的问题

2023-03-22 08:54:19 浏览数 (1)

在完成一个需求时,我发现有个函数是这样写的:

代码语言:go复制
func test(names []string) {
	for _, name := range names {
		doSomething(name)
	}
}

观察逻辑发现这个数组中的每个元素执行起来,其实没有必然的先后关系,完全可以并发执行,于是我改了一版代码:

代码语言:go复制
func test(names []string) {
	for _, name := range names {
		go func() {
			doSomething(name)
		}
	}
	time.Sleep(time.Second)
}

执行后令我疑惑的事情发生了,假设names="Zhao", "Qian", "Sun",那么执行的结果里只有"Sun"的记录。

由于当时刚接触Golang,想了半天也不知道为什么。但是后来我自己想明白了,for循环属于主goroutine,主goroutine才不会管其他goroutine是否被执行了。而我的goroutine是在循环结束的时候才执行的,这个时候的name就一定已经是"Sun"了。

比如这段代码,接近100%的概率什么都不会打印出来,这就是因为主goroutine只会自顾自的执行自己的工作,结束后则退出,那个时候一切都结束了:

代码语言:go复制
fun main() {
	go fmt.Println("Hello")
}

接下来做了另外一版的修改,这样就很完美了:

代码语言:go复制
func test(names []string) {
	for _, name := range names {
		go func(n string) {
			doSomething(n)
		}(name)
	}
	time.Sleep(time.Second)
}

将name作为一个参数传入闭包中。此时函数内的name不会受到外部影响,这样就可以执行出正确的结果了。

这里有一点需要注意,由于name是string类型的,属于非引用类型,在当做参数被传入的时候,是会将其复制一份传入的,此时的入参就成了完全独立的存在,不受外部影响。

不过这个代码实现给我的感觉依旧很愚蠢,因为加上了一个sleep方法。如果有一个name的执行时间(或者调用接口网络抖动)超过了1s,当然主goroutine还是不会等它执行完成就会退出,会导致一些不可预见的问题发生。

总不可能无限制的增加sleep时长来换取安全性。实际上这些names是可以协同等待的,等待所有的goroutine执行结束之后,一起退出。这种模式有点类似于Java的CountDownLatch,在Golang中可以借助sync.WaitGroup来实现。

go

0 人点赞